C++中的并行與并發(fā)基礎(chǔ)與使用詳解
1. 并行基礎(chǔ)
std::thread 用于創(chuàng)建一個(gè)執(zhí)行的線程實(shí)例,所以它是一切并發(fā)編程的基礎(chǔ),使用時(shí)需要包含 <thread> 頭文件, 它提供了很多基本的線程操作,例如 get_id() 來(lái)獲取所創(chuàng)建線程的線程 ID,使用 join() 來(lái)加入一個(gè)線程等等,例如:
#include <iostream> #include <thread> int main() { std::thread t([](){ std::cout << "hello world." << std::endl; }); t.join(); return 0; }
2. 互斥量與臨界區(qū)
我們?cè)诓僮飨到y(tǒng)、亦或是數(shù)據(jù)庫(kù)的相關(guān)知識(shí)中已經(jīng)了解過(guò)了有關(guān)并發(fā)技術(shù)的基本知識(shí),mutex 就是其中的核心之一。 C++11 引入了 mutex 相關(guān)的類,其所有相關(guān)的函數(shù)都放在 <mutex> 頭文件中。
std::mutex 是 C++11 中最基本的 mutex 類,通過(guò)實(shí)例化 std::mutex 可以創(chuàng)建互斥量, 而通過(guò)其成員函數(shù) lock() 可以進(jìn)行上鎖,unlock() 可以進(jìn)行解鎖。 但是在實(shí)際編寫代碼的過(guò)程中,最好不去直接調(diào)用成員函數(shù), 因?yàn)檎{(diào)用成員函數(shù)就需要在每個(gè)臨界區(qū)的出口處調(diào)用 unlock(),當(dāng)然,還包括異常。 這時(shí)候 C++11 還為互斥量提供了一個(gè) RAII 語(yǔ)法的模板類 std::lock_guard。 RAII 在不失代碼簡(jiǎn)潔性的同時(shí),很好的保證了代碼的異常安全性。
在 RAII 用法下,對(duì)于臨界區(qū)的互斥量的創(chuàng)建只需要在作用域的開(kāi)始部分,例如:
#include <iostream> #include <mutex> #include <thread> int v = 1; void critical_section(int change_v) { static std::mutex mtx; std::lock_guard<std::mutex> lock(mtx); // 執(zhí)行競(jìng)爭(zhēng)操作 v = change_v; // 離開(kāi)此作用域后 mtx 會(huì)被釋放 } int main() { std::thread t1(critical_section, 2), t2(critical_section, 3); t1.join(); t2.join(); std::cout << v << std::endl; return 0; }
由于 C++ 保證了所有棧對(duì)象在生命周期結(jié)束時(shí)會(huì)被銷毀,所以這樣的代碼也是異常安全的。 無(wú)論 critical_section() 正常返回、還是在中途拋出異常,都會(huì)引發(fā)堆?;赝?,也就自動(dòng)調(diào)用了 unlock()。
而 std::unique_lock 則是相對(duì)于 std::lock_guard 出現(xiàn)的,std::unique_lock 更加靈活, std::unique_lock 的對(duì)象會(huì)以獨(dú)占所有權(quán)(沒(méi)有其他的 unique_lock 對(duì)象同時(shí)擁有某個(gè) mutex 對(duì)象的所有權(quán)) 的方式管理 mutex 對(duì)象上的上鎖和解鎖的操作。所以在并發(fā)編程中,推薦使用 std::unique_lock。
std::lock_guard 不能顯式的調(diào)用 lock 和 unlock, 而 std::unique_lock 可以在聲明后的任意位置調(diào)用, 可以縮小鎖的作用范圍,提供更高的并發(fā)度。
如果你用到了條件變量 std::condition_variable::wait 則必須使用 std::unique_lock 作為參數(shù)。
例如:
#include <iostream> #include <mutex> #include <thread> int v = 1; void critical_section(int change_v) { static std::mutex mtx; std::unique_lock<std::mutex> lock(mtx); // 執(zhí)行競(jìng)爭(zhēng)操作 v = change_v; std::cout << v << std::endl; // 將鎖進(jìn)行釋放 lock.unlock(); // 在此期間,任何人都可以搶奪 v 的持有權(quán) // 開(kāi)始另一組競(jìng)爭(zhēng)操作,再次加鎖 lock.lock(); v += 1; std::cout << v << std::endl; } int main() { std::thread t1(critical_section, 2), t2(critical_section, 3); t1.join(); t2.join(); return 0; }
3. 期物
期物(Future)表現(xiàn)為 std::future,它提供了一個(gè)訪問(wèn)異步操作結(jié)果的途徑,這句話很不好理解。 為了理解這個(gè)特性,我們需要先理解一下在 C++11 之前的多線程行為。
試想,如果我們的主線程 A 希望新開(kāi)辟一個(gè)線程 B 去執(zhí)行某個(gè)我們預(yù)期的任務(wù),并返回我一個(gè)結(jié)果。 而這時(shí)候,線程 A 可能正在忙其他的事情,無(wú)暇顧及 B 的結(jié)果, 所以我們會(huì)很自然的希望能夠在某個(gè)特定的時(shí)間獲得線程 B 的結(jié)果。
在 C++11 的 std::future 被引入之前,通常的做法是: 創(chuàng)建一個(gè)線程 A,在線程 A 里啟動(dòng)任務(wù) B,當(dāng)準(zhǔn)備完畢后發(fā)送一個(gè)事件,并將結(jié)果保存在全局變量中。 而主函數(shù)線程 A 里正在做其他的事情,當(dāng)需要結(jié)果的時(shí)候,調(diào)用一個(gè)線程等待函數(shù)來(lái)獲得執(zhí)行的結(jié)果。
而 C++11 提供的 std::future 簡(jiǎn)化了這個(gè)流程,可以用來(lái)獲取異步任務(wù)的結(jié)果。 自然地,我們很容易能夠想象到把它作為一種簡(jiǎn)單的線程同步手段,即屏障(barrier)。
為了看一個(gè)例子,我們這里額外使用 std::packaged_task,它可以用來(lái)封裝任何可以調(diào)用的目標(biāo),從而用于實(shí)現(xiàn)異步的調(diào)用。 舉例來(lái)說(shuō):
#include <iostream> #include <future> #include <thread> int main() { // 將一個(gè)返回值為7的 lambda 表達(dá)式封裝到 task 中 // std::packaged_task 的模板參數(shù)為要封裝函數(shù)的類型 std::packaged_task<int()> task([](){return 7;}); // 獲得 task 的期物 std::future<int> result = task.get_future(); // 在一個(gè)線程中執(zhí)行 task std::thread(std::move(task)).detach(); std::cout << "waiting..."; result.wait(); // 在此設(shè)置屏障,阻塞到期物的完成 // 輸出執(zhí)行結(jié)果 std::cout << "done!" << std:: endl << "future result is " << result.get() << std::endl; return 0; }
在封裝好要調(diào)用的目標(biāo)后,可以使用 get_future() 來(lái)獲得一個(gè) std::future 對(duì)象,以便之后實(shí)施線程同步。
4. 條件變量
條件變量 std::condition_variable 是為了解決死鎖而生,當(dāng)互斥操作不夠用而引入的。 比如,線程可能需要等待某個(gè)條件為真才能繼續(xù)執(zhí)行, 而一個(gè)忙等待循環(huán)中可能會(huì)導(dǎo)致所有其他線程都無(wú)法進(jìn)入臨界區(qū)使得條件為真時(shí),就會(huì)發(fā)生死鎖。 所以,condition_variable 實(shí)例被創(chuàng)建出現(xiàn)主要就是用于喚醒等待線程從而避免死鎖。 std::condition_variable的 notify_one() 用于喚醒一個(gè)線程; notify_all() 則是通知所有線程。下面是一個(gè)生產(chǎn)者和消費(fèi)者模型的例子:
#include <queue> #include <chrono> #include <mutex> #include <thread> #include <iostream> #include <condition_variable> int main() { std::queue<int> produced_nums; std::mutex mtx; std::condition_variable cv; bool notified = false; // 通知信號(hào) // 生產(chǎn)者 auto producer = [&]() { for (int i = 0; ; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(900)); std::unique_lock<std::mutex> lock(mtx); std::cout << "producing " << i << std::endl; produced_nums.push(i); notified = true; cv.notify_all(); // 此處也可以使用 notify_one } }; // 消費(fèi)者 auto consumer = [&]() { while (true) { std::unique_lock<std::mutex> lock(mtx); while (!notified) { // 避免虛假喚醒 cv.wait(lock); } // 短暫取消鎖,使得生產(chǎn)者有機(jī)會(huì)在消費(fèi)者消費(fèi)空前繼續(xù)生產(chǎn) lock.unlock(); // 消費(fèi)者慢于生產(chǎn)者 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); lock.lock(); while (!produced_nums.empty()) { std::cout << "consuming " << produced_nums.front() << std::endl; produced_nums.pop(); } notified = false; } }; // 分別在不同的線程中運(yùn)行 std::thread p(producer); std::thread cs[2]; for (int i = 0; i < 2; ++i) { cs[i] = std::thread(consumer); } p.join(); for (int i = 0; i < 2; ++i) { cs[i].join(); } return 0; }
值得一提的是,在生產(chǎn)者中我們雖然可以使用 notify_one(),但實(shí)際上并不建議在此處使用, 因?yàn)樵诙嘞M(fèi)者的情況下,我們的消費(fèi)者實(shí)現(xiàn)中簡(jiǎn)單放棄了鎖的持有,這使得可能讓其他消費(fèi)者 爭(zhēng)奪此鎖,從而更好的利用多個(gè)消費(fèi)者之間的并發(fā)。話雖如此,但實(shí)際上因?yàn)?std::mutex 的排他性, 我們根本無(wú)法期待多個(gè)消費(fèi)者能真正意義上的并行消費(fèi)隊(duì)列的中生產(chǎn)的內(nèi)容,我們?nèi)孕枰6雀?xì)的手段。
5. 原子操作與內(nèi)存模型
細(xì)心的讀者可能會(huì)對(duì)前一小節(jié)中生產(chǎn)者消費(fèi)者模型的例子可能存在編譯器優(yōu)化導(dǎo)致程序出錯(cuò)的情況產(chǎn)生疑惑。 例如,布爾值 notified 沒(méi)有被 volatile 修飾,編譯器可能對(duì)此變量存在優(yōu)化,例如將其作為一個(gè)寄存器的值, 從而導(dǎo)致消費(fèi)者線程永遠(yuǎn)無(wú)法觀察到此值的變化。這是一個(gè)好問(wèn)題,為了解釋清楚這個(gè)問(wèn)題,我們需要進(jìn)一步討論 從 C++ 11 起引入的內(nèi)存模型這一概念。我們首先來(lái)看一個(gè)問(wèn)題,下面這段代碼輸出結(jié)果是多少?
#include <thread> #include <iostream> int main() { int a = 0; int flag = 0; std::thread t1([&]() { while (flag != 1); int b = a; std::cout << "b = " << b << std::endl; }); std::thread t2([&]() { a = 5; flag = 1; }); t1.join(); t2.join(); return 0; }
從直觀上看,t2 中 a = 5; 這一條語(yǔ)句似乎總在 flag = 1; 之前得到執(zhí)行,而 t1 中 while (flag != 1) 似乎保證了 std::cout << "b = " << b << std::endl; 不會(huì)再標(biāo)記被改變前執(zhí)行。從邏輯上看,似乎 b 的值應(yīng)該等于 5。 但實(shí)際情況遠(yuǎn)比此復(fù)雜得多,或者說(shuō)這段代碼本身屬于未定義的行為,因?yàn)閷?duì)于 a 和 flag 而言,他們?cè)趦蓚€(gè)并行的線程中被讀寫, 出現(xiàn)了競(jìng)爭(zhēng)。除此之外,即便我們忽略競(jìng)爭(zhēng)讀寫,仍然可能受 CPU 的亂序執(zhí)行,編譯器對(duì)指令的重排的影響, 導(dǎo)致 a = 5 發(fā)生在 flag = 1 之后。從而 b 可能輸出 0。
5.1原子操作
std::mutex 可以解決上面出現(xiàn)的并發(fā)讀寫的問(wèn)題,但互斥鎖是操作系統(tǒng)級(jí)的功能, 這是因?yàn)橐粋€(gè)互斥鎖的實(shí)現(xiàn)通常包含兩條基本原理:
提供線程間自動(dòng)的狀態(tài)轉(zhuǎn)換,即『鎖住』這個(gè)狀態(tài)
保障在互斥鎖操作期間,所操作變量的內(nèi)存與臨界區(qū)外進(jìn)行隔離
這是一組非常強(qiáng)的同步條件,換句話說(shuō)當(dāng)最終編譯為 CPU 指令時(shí)會(huì)表現(xiàn)為非常多的指令(我們之后再來(lái)看如何實(shí)現(xiàn)一個(gè)簡(jiǎn)單的互斥鎖)。 這對(duì)于一個(gè)僅需原子級(jí)操作(沒(méi)有中間態(tài))的變量,似乎太苛刻了。
關(guān)于同步條件的研究有著非常久遠(yuǎn)的歷史,我們?cè)谶@里不進(jìn)行贅述。讀者應(yīng)該明白,現(xiàn)代 CPU 體系結(jié)構(gòu)提供了 CPU 指令級(jí)的原子操作, 因此在 C++11 中多線程下共享變量的讀寫這一問(wèn)題上,還引入了 std::atomic 模板,使得我們實(shí)例化一個(gè)原子類型,將一個(gè) 原子類型讀寫操作從一組指令,最小化到單個(gè) CPU 指令。例如:
std::atomic<int> counter;
并為整數(shù)或浮點(diǎn)數(shù)的原子類型提供了基本的數(shù)值成員函數(shù),舉例來(lái)說(shuō), 包括 fetch_add, fetch_sub 等,同時(shí)通過(guò)重載方便的提供了對(duì)應(yīng)的 +,- 版本。 比如下面的例子:
#include <atomic> #include <thread> #include <iostream> std::atomic<int> count = {0}; int main() { std::thread t1([](){ count.fetch_add(1); }); std::thread t2([](){ count++; // 等價(jià)于 fetch_add count += 1; // 等價(jià)于 fetch_add }); t1.join(); t2.join(); std::cout << count << std::endl; return 0; }
當(dāng)然,并非所有的類型都能提供原子操作,這是因?yàn)樵硬僮鞯目尚行匀Q于具體的 CPU 架構(gòu),以及所實(shí)例化的類型結(jié)構(gòu)是否能夠滿足該 CPU 架構(gòu)對(duì)內(nèi)存對(duì)齊 條件的要求,因而我們總是可以通過(guò) std::atomic<T>::is_lock_free 來(lái)檢查該原子類型是否需支持原子操作,例如:
#include <atomic> #include <iostream> struct A { float x; int y; long long z; }; int main() { std::atomic<A> a; std::cout << std::boolalpha << a.is_lock_free() << std::endl; return 0; }
5.2一致性模型
并行執(zhí)行的多個(gè)線程,從某種宏觀層面上討論,可以粗略的視為一種分布式系統(tǒng)。 在分布式系統(tǒng)中,任何通信乃至本地操作都需要消耗一定時(shí)間,甚至出現(xiàn)不可靠的通信。
如果我們強(qiáng)行將一個(gè)變量 v 在多個(gè)線程之間的操作設(shè)為原子操作,即任何一個(gè)線程在操作完 v 后, 其他線程均能同步感知到 v 的變化,則對(duì)于變量 v 而言,表現(xiàn)為順序執(zhí)行的程序,它并沒(méi)有由于引入多線程 而得到任何效率上的收益。對(duì)此有什么辦法能夠適當(dāng)?shù)募铀倌??答案便是削弱原子操作的在進(jìn)程間的同步條件。
從原理上看,每個(gè)線程可以對(duì)應(yīng)為一個(gè)集群節(jié)點(diǎn),而線程間的通信也幾乎等價(jià)于集群節(jié)點(diǎn)間的通信。 削弱進(jìn)程間的同步條件,通常我們會(huì)考慮四種不同的一致性模型:
線性一致性:又稱強(qiáng)一致性或原子一致性。它要求任何一次讀操作都能讀到某個(gè)數(shù)據(jù)的最近一次寫的數(shù)據(jù),并且所有線程的操作順序與全局時(shí)鐘下的順序是一致的。
x.store(1) x.load()
T1 ---------+----------------+------>
T2 -------------------+------------->
x.store(2)
在這種情況下線程 T1, T2 對(duì) x 的兩次寫操作是原子的,且 x.store(1) 是嚴(yán)格的發(fā)生在 x.store(2) 之前,x.store(2) 嚴(yán)格的發(fā)生在 x.load() 之前。 值得一提的是,線性一致性對(duì)全局時(shí)鐘的要求是難以實(shí)現(xiàn)的,這也是人們不斷研究比這個(gè)一致性更弱條件下其他一致性的算法的原因。
順序一致性:同樣要求任何一次讀操作都能讀到數(shù)據(jù)最近一次寫入的數(shù)據(jù),但未要求與全局時(shí)鐘的順序一致。
x.store(1) x.store(3) x.load()
T1 ---------+-----------+----------+----->
T2 ---------------+---------------------->
x.store(2)
或者
x.store(1) x.store(3) x.load()
T1 ---------+-----------+----------+----->
T2 ------+------------------------------->
x.store(2)
在順序一致性的要求下,x.load() 必須讀到最近一次寫入的數(shù)據(jù),因此 x.store(2) 與 x.store(1) 并無(wú)任何先后保障,即 只要 T2 的 x.store(2) 發(fā)生在 x.store(3) 之前即可。
因果一致性:它的要求進(jìn)一步降低,只需要有因果關(guān)系的操作順序得到保障,而非因果關(guān)系的操作順序則不做要求。
a = 1 b = 2
T1 ----+-----------+---------------------------->
T2 ------+--------------------+--------+-------->
x.store(3) c = a + b y.load()
或者
a = 1 b = 2
T1 ----+-----------+---------------------------->
T2 ------+--------------------+--------+-------->
x.store(3) y.load() c = a + b
亦或者
b = 2 a = 1
T1 ----+-----------+---------------------------->
T2 ------+--------------------+--------+-------->
y.load() c = a + b x.store(3)
上面給出的三種例子都是屬于因果一致的,因?yàn)檎麄€(gè)過(guò)程中,只有 c 對(duì) a 和 b 產(chǎn)生依賴,而 x 和 y 在此例子中表現(xiàn)為沒(méi)有關(guān)系(但實(shí)際情況中我們需要更詳細(xì)的信息才能確定 x 與 y 確實(shí)無(wú)關(guān))
最終一致性:是最弱的一致性要求,它只保障某個(gè)操作在未來(lái)的某個(gè)時(shí)間節(jié)點(diǎn)上會(huì)被觀察到,但并未要求被觀察到的時(shí)間。因此我們甚至可以對(duì)此條件稍作加強(qiáng),例如規(guī)定某個(gè)操作被觀察到的時(shí)間總是有界的。當(dāng)然這已經(jīng)不在我們的討論范圍之內(nèi)了。
x.store(3) x.store(4)
T1 ----+-----------+-------------------------------------------->
T2 ---------+------------+--------------------+--------+-------->
x.read x.read() x.read() x.read()
在上面的情況中,如果我們假設(shè) x 的初始值為 0,則 T2 中四次 x.read() 結(jié)果可能但不限于以下情況:
3 4 4 4 // x 的寫操作被很快觀察到
0 3 3 4 // x 的寫操作被觀察到的時(shí)間存在一定延遲
0 0 0 4 // 最后一次讀操作讀到了 x 的最終值,但此前的變化并未觀察到
0 0 0 0 // 在當(dāng)前時(shí)間段內(nèi) x 的寫操作均未被觀察到,
// 但未來(lái)某個(gè)時(shí)間點(diǎn)上一定能觀察到 x 為 4 的情況
5.3內(nèi)存順序
為了追求極致的性能,實(shí)現(xiàn)各種強(qiáng)度要求的一致性,C++11 為原子操作定義了六種不同的內(nèi)存順序 std::memory_order 的選項(xiàng),表達(dá)了四種多線程間的同步模型:
寬松模型:在此模型下,單個(gè)線程內(nèi)的原子操作都是順序執(zhí)行的,不允許指令重排,但不同線程間原子操作的順序是任意的。類型通過(guò) std::memory_order_relaxed 指定。我們來(lái)看一個(gè)例子:
std::atomic<int> counter = {0}; std::vector<std::thread> vt; for (int i = 0; i < 100; ++i) { vt.emplace_back([&](){ counter.fetch_add(1, std::memory_order_relaxed); }); } for (auto& t : vt) { t.join(); } std::cout << "current counter:" << counter << std::endl;
釋放/消費(fèi)模型:在此模型中,我們開(kāi)始限制進(jìn)程間的操作順序,如果某個(gè)線程需要修改某個(gè)值,但另一個(gè)線程會(huì)對(duì)該值的某次操作產(chǎn)生依賴,即后者依賴前者。具體而言,線程 A 完成了三次對(duì) x 的寫操作,線程 B 僅依賴其中第三次 x 的寫操作,與 x 的前兩次寫行為無(wú)關(guān),則當(dāng) A 主動(dòng) x.release() 時(shí)候(即使用 std::memory_order_release),選項(xiàng) std::memory_order_consume 能夠確保 B 在調(diào)用 x.load() 時(shí)候觀察到 A 中第三次對(duì) x 的寫操作。我們來(lái)看一個(gè)例子:
// 初始化為 nullptr 防止 consumer 線程從野指針進(jìn)行讀取 std::atomic<int*> ptr(nullptr); int v; std::thread producer([&]() { int* p = new int(42); v = 1024; ptr.store(p, std::memory_order_release); }); std::thread consumer([&]() { int* p; while(!(p = ptr.load(std::memory_order_consume))); std::cout << "p: " << *p << std::endl; std::cout << "v: " << v << std::endl; }); producer.join(); consumer.join();
釋放/獲取模型:在此模型下,我們可以進(jìn)一步加緊對(duì)不同線程間原子操作的順序的限制,在釋放 std::memory_order_release 和獲取 std::memory_order_acquire 之間規(guī)定時(shí)序,即發(fā)生在釋放(release)操作之前的所有寫操作,對(duì)其他線程的任何獲?。╝cquire)操作都是可見(jiàn)的,亦即發(fā)生順序(happens-before)。
可以看到,std::memory_order_release 確保了它之前的寫操作不會(huì)發(fā)生在釋放操作之后,是一個(gè)向后的屏障(backward),而 std::memory_order_acquire 確保了它之前的寫行為不會(huì)發(fā)生在該獲取操作之后,是一個(gè)向前的屏障(forward)。對(duì)于選項(xiàng) std::memory_order_acq_rel 而言,則結(jié)合了這兩者的特點(diǎn),唯一確定了一個(gè)內(nèi)存屏障,使得當(dāng)前線程對(duì)內(nèi)存的讀寫不會(huì)被重排并越過(guò)此操作的前后:
我們來(lái)看一個(gè)例子:
std::vector<int> v; std::atomic<int> flag = {0}; std::thread release([&]() { v.push_back(42); flag.store(1, std::memory_order_release); }); std::thread acqrel([&]() { int expected = 1; // must before compare_exchange_strong while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) expected = 1; // must after compare_exchange_strong // flag has changed to 2 }); std::thread acquire([&]() { while(flag.load(std::memory_order_acquire) < 2); std::cout << v.at(0) << std::endl; // must be 42 }); release.join(); acqrel.join(); acquire.join();
在此例中我們使用了 compare_exchange_strong 比較交換原語(yǔ)(Compare-and-swap primitive),它有一個(gè)更弱的版本,即 compare_exchange_weak,它允許即便交換成功,也仍然返回 false 失敗。其原因是因?yàn)樵谀承┢脚_(tái)上虛假故障導(dǎo)致的,具體而言,當(dāng) CPU 進(jìn)行上下文切換時(shí),另一線程加載同一地址產(chǎn)生的不一致。除此之外,compare_exchange_strong 的性能可能稍差于 compare_exchange_weak,但大部分情況下,鑒于其使用的復(fù)雜度而言,compare_exchange_weak 應(yīng)該被有限考慮。
順序一致模型:在此模型下,原子操作滿足順序一致性,進(jìn)而可能對(duì)性能產(chǎn)生損耗??娠@式的通過(guò) std::memory_order_seq_cst 進(jìn)行指定。最后來(lái)看一個(gè)例子: std::atomic<int> counter = {0}; std::vector<std::thread> vt; for (int i = 0; i < 100; ++i) { vt.emplace_back([&](){ counter.fetch_add(1, std::memory_order_seq_cst); }); } for (auto& t : vt) { t.join(); } std::cout << "current counter:" << counter << std::endl;
這個(gè)例子與第一個(gè)寬松模型的例子本質(zhì)上沒(méi)有區(qū)別,僅僅只是將原子操作的內(nèi)存順序修改為了 memory_order_seq_cst,有興趣的讀者可以自行編寫程序測(cè)量這兩種不同內(nèi)存順序?qū)е碌男阅懿町悺?/p>
到此這篇關(guān)于C++中的并行與并發(fā)基礎(chǔ)與使用詳解的文章就介紹到這了,更多相關(guān)C++并行與并發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
VC++ 字符串String MD5計(jì)算小工具 VS2008工程
基于字符串加密的MD5算法,VS2008 VC++,多字節(jié)編譯工程。主要代碼如下,實(shí)現(xiàn)了ANSI字符串加密與Unicode字符串加密,需要的朋友可以參考下2017-07-07C語(yǔ)言實(shí)現(xiàn)查看進(jìn)程是否存在的方法示例
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)查看進(jìn)程是否存在的方法,涉及C語(yǔ)言針對(duì)進(jìn)程操作的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-07-07OpenCV實(shí)現(xiàn)視頻綠幕背景替換功能的示例代碼
這篇文章主要介紹了如何利用OpenCV實(shí)現(xiàn)視頻綠幕背景替換功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)OpenCV有一定的幫助,感興趣的可以學(xué)習(xí)一下2023-02-02C語(yǔ)言中volatile關(guān)鍵字的深入講解
在程序設(shè)計(jì)中,尤其是在C語(yǔ)言、C++、C#和Java語(yǔ)言中,使用volatile關(guān)鍵字聲明的變量或?qū)ο笸ǔ>哂信c優(yōu)化、多線程相關(guān)的特殊屬性,這篇文章主要給大家介紹了關(guān)于C語(yǔ)言volatile關(guān)鍵字的相關(guān)資料,需要的朋友可以參考下2021-07-07iostream與iostream.h的區(qū)別詳細(xì)解析
以下是對(duì)C++中iostream與iostream.h的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-09-09