現(xiàn)代 C++ 并發(fā)編程基礎(chǔ)
并行基礎(chǔ)
std::thread?用于創(chuàng)建一個(gè)執(zhí)行的線程實(shí)例,所以它是一切并發(fā)編程的基礎(chǔ),使用時(shí)需要包含?
#include |
互斥量與臨界區(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ù)都放在?
std::mutex?是 C++11 中最基本的?mutex?類,通過(guò)實(shí)例化?std::mutex?可以創(chuàng)建互斥量, 而通過(guò)其成員函數(shù) lock()?可以進(jìn)行上鎖,unlock()?可以進(jìn)行解鎖。
但是在實(shí)際編寫(xiě)代碼的過(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 |
由于 C++ 保證了所有棧對(duì)象在生命周期結(jié)束時(shí)會(huì)被銷(xiāo)毀,所以這樣的代碼也是異常安全的。無(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 |
期物
期物(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 |
在封裝好要調(diào)用的目標(biāo)后,可以使用?get_future()?來(lái)獲得一個(gè)?std::future?對(duì)象,以便之后實(shí)施線程同步。
條件變量
條件變量?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 |
值得一提的是,在生產(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ì)的手段。
來(lái)源:https://changkun.de/modern-cpp/zh-cn/07-thread/index.html
推薦:
Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能
