日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當(dāng)前位置:首頁(yè) > > 充電吧
[導(dǎo)讀]背景?C/C++語(yǔ)言的并發(fā)程序(Concurrent Programming)設(shè)計(jì),一直是一個(gè)比較困難的話題。很多朋友都會(huì)嘗試使用多線程編程,但是卻很難保證自己所寫的多線程程序的正確性。多線程程序,如

背景

?

C/C++語(yǔ)言的并發(fā)程序(Concurrent Programming)設(shè)計(jì),一直是一個(gè)比較困難的話題。很多朋友都會(huì)嘗試使用多線程編程,但是卻很難保證自己所寫的多線程程序的正確性。多線程程序,如果涉及到對(duì)共享資源的并發(fā)讀寫,就會(huì)產(chǎn)生資源爭(zhēng)用(Data Race)。解決資源爭(zhēng)用,最直接的想法是引入鎖,對(duì)并發(fā)讀寫的數(shù)據(jù)進(jìn)行保護(hù)(更高級(jí)的則包括無(wú)鎖編程—— Lock Free Programming)。但是,鎖又有很多種類,例如:自旋鎖(Spinlock)、互斥鎖(Mutex)、讀寫鎖(Read-Write-Lock)等等。這么多的鎖,每種鎖有什么特點(diǎn)?對(duì)應(yīng)哪些不同的使用場(chǎng)景?使用過(guò)程中需要注意哪些事項(xiàng)?各自分別有哪些不足之處?都是困擾程序員的一個(gè)個(gè)問(wèn)題。

?

甚至,一個(gè)最基本的問(wèn)題:為什么鎖就能夠用來(lái)保護(hù)共享資源?鎖真正蘊(yùn)含的意義有哪些?我相信很多使用過(guò)各種鎖的程序員,都不一定能夠完全正確的回答出來(lái)。

?

有鑒于此,本人希望將自己近10年數(shù)據(jù)庫(kù)內(nèi)核研發(fā),所積累下的并發(fā)編程的經(jīng)驗(yàn)記錄下來(lái),形成一個(gè)系列的文章,分享給大家。這個(gè)系列,個(gè)人打算對(duì)其命名為 #并發(fā)編程系列# ,作為此系列開(kāi)篇的文章,本文將從一個(gè)簡(jiǎn)單的并發(fā)編程的例子出發(fā),引出鎖真正蘊(yùn)含的意義。

?

一個(gè)測(cè)試用例

?

并發(fā)程序處理中,面臨的一個(gè)最簡(jiǎn)單,也是最常見(jiàn)的共享資源的爭(zhēng)用,就是針對(duì)一個(gè)全局變量進(jìn)行并發(fā)的更新和讀取。這個(gè)全局變量,可以是一個(gè)全局計(jì)數(shù)器,統(tǒng)計(jì)某個(gè)事件在多線程中發(fā)生的次數(shù);或者是一個(gè)全局遞增序列,每個(gè)線程需要獲取屬于其的唯一標(biāo)識(shí)。諸如此類,多個(gè)線程,針對(duì)一個(gè)全局變量的并發(fā)讀寫,是十分常見(jiàn)的。如下圖所示:

此用例中,N個(gè)線程,并發(fā)更新一個(gè)全局變量。讓我們先來(lái)看一個(gè)簡(jiǎn)單的測(cè)試,全局變量global_count沒(méi)有任何保護(hù),此時(shí)會(huì)發(fā)生什么?

?

測(cè)試場(chǎng)景:500個(gè)線程,每個(gè)線程做10000次global_count++操作,主線程首先將global_count初始化為0,然后等待這500線程運(yùn)行結(jié)束。待500個(gè)線程運(yùn)行結(jié)束之后,由于每個(gè)線程都做了10000次global_count++,那么可以確定,最后的global_count取值應(yīng)該是5000000。事實(shí)是這樣嗎?根據(jù)此測(cè)試場(chǎng)景,撰寫測(cè)試代碼,每個(gè)線程做的都是同樣的事,代碼很簡(jiǎn)單:

主線程等待所有500個(gè)線程結(jié)束之后,進(jìn)行判斷,若global_count不等于5000000,則打印出當(dāng)前global_count的取值。運(yùn)行結(jié)果如下:

通過(guò)上圖,可以發(fā)現(xiàn),global_count并不是每次都等于5000000,很大的幾率,global_count要小于5000000。多線程對(duì)一個(gè)全局變量進(jìn)行++操作,并不能保證最終得到的結(jié)果的正確性。究其內(nèi)部原因,是因?yàn)?+操作并不是一個(gè)原子操作(Atomic Operation),而是對(duì)應(yīng)至少3條匯編語(yǔ)句,考慮如下兩個(gè)線程的 ++ 操作并發(fā):

線程1,2,分別讀取了global_count的當(dāng)前值,分別加1后寫出。線程2的寫覆蓋了線程1的寫,最后導(dǎo)致兩次 ++ 操作,實(shí)際上卻對(duì)global_count只加了1次。

?

如何解決此問(wèn)題,相信大家都有很多方法,例如:將global_count聲明為原子變量(C++ 11標(biāo)準(zhǔn)支持)。但是此文,并不打算使用原子變量,而是將global_count的++操作,通過(guò)Spinlock保護(hù)起來(lái)。一個(gè)全局的Spinlock,500個(gè)線程,在++操作前,需要獲取Spinlock,然后進(jìn)行g(shù)lobal_count的++操作,完成后釋放Spinlock。對(duì)應(yīng)的每個(gè)線程代碼修改如下:

主線程,仍舊是同樣的邏輯,等待所有的500個(gè)線程執(zhí)行結(jié)束之后,判斷global_count取值是否等于5000000,如果不相等,則打印出來(lái)。此時(shí),同樣執(zhí)行此測(cè)試程序,沒(méi)有任何一條數(shù)據(jù)打印出來(lái),每一個(gè)循環(huán),都滿足global_count等于5000000。通過(guò)引入了Spinlock,完美了解決上面的問(wèn)題。

?

為什么引入了Spinlock保護(hù)之后,多線程針對(duì)全局變量的并發(fā)讀寫所帶來(lái)的問(wèn)題就解決了?此問(wèn)題,恰好引入了鎖意義的剖析。

?

鎖的意義

?

在分析鎖的意義前,先來(lái)簡(jiǎn)單看看Spinlock的功能:Spinlock是一把互斥鎖,同一時(shí)間,只能有一個(gè)線程持有Spinlock鎖,而所有其他的線程,處于等待Spinlock鎖。當(dāng)持有Spinlock的線程放鎖之后,所有等待獲取Spinlock的線程一起爭(zhēng)搶,一個(gè)Lucky的線程,搶到這把鎖,大部分Unlucky的線程,只能繼續(xù)等待下一次搶鎖的機(jī)會(huì)。

?

由此來(lái)說(shuō),在spinlock鎖保護(hù)下的代碼片段,同一時(shí)間只能有一個(gè)線程(獲得Spinlock的線程)能夠執(zhí)行,而其他的線程,在獲取spinlock之前,不可進(jìn)入spinlock鎖保護(hù)下的代碼片段進(jìn)行執(zhí)行。前面的測(cè)試用例,由于spinlock保護(hù)了global_count++的代碼,因此global_count++操作,同時(shí)只能有一個(gè)線程執(zhí)行,不可能出現(xiàn)前面提到的兩線程并發(fā)修改global_count變量出現(xiàn)的問(wèn)題。How Perfect?。。。ㄗⅲ涸趕pinlock加鎖之前,以及spinlock放鎖之后的代碼段,可以由多線程并發(fā)執(zhí)行。)

但是,故事到此就完了嗎?我相信對(duì)于大部分程序員來(lái)說(shuō),或者是之前的我來(lái)說(shuō),認(rèn)為故事到此就結(jié)束了。已經(jīng)成功的使用了一個(gè)Spinlock,來(lái)保護(hù)全局變量的并發(fā)讀寫,保證了并發(fā)訪問(wèn)的正確性。

?

但是(又是這個(gè)該死的但是),故事并未結(jié)束,這個(gè)案子也還沒(méi)有了結(jié)。有一定經(jīng)驗(yàn)的C/C++程序員,或者是曾經(jīng)看過(guò)我寫過(guò)的一個(gè)PPT:《CPU Cache and Memory Ordering——并發(fā)程序設(shè)計(jì)入門》,以及一篇博客:《C/C++ Volatile關(guān)鍵詞深度剖析》,的朋友來(lái)說(shuō),應(yīng)該都知道這個(gè)故事還有一個(gè)點(diǎn)沒(méi)有挖掘:內(nèi)存模型(Memory Model),無(wú)論是程序語(yǔ)言(如:C/C++,Java),或者是CPU(如:Intel X86,Power PC,ARM),都有所謂的內(nèi)存模型。

?

簡(jiǎn)單來(lái)說(shuō),內(nèi)存模型規(guī)定了一種內(nèi)存操作可見(jiàn)的順序。為了提高程序運(yùn)行的效率,編譯器可能會(huì)對(duì)你寫的程序進(jìn)行重寫,執(zhí)行順序調(diào)整等等,同樣,CPU也會(huì)對(duì)其執(zhí)行的匯編執(zhí)行進(jìn)行順序的調(diào)整,這就是所謂的亂序執(zhí)行。最基本的四種亂序行為,包含:LoadLoad亂序;LoadStore亂序;StoreLoad亂序;StoreStore亂序,分別對(duì)應(yīng)于讀讀亂序,讀寫亂序,寫讀亂序,寫寫亂序。關(guān)于這四種亂序行為更為詳細(xì)的介紹,可參考Preshing的博客:《Memory Reordering Caught in the Act》,《Memory Barriers Are Like Source Control Operations》,或者是《SMP Primer for Android》這篇文章。本文接下來(lái)的部分,假設(shè)讀者已經(jīng)知道了無(wú)論是編譯器,還是CPU,都會(huì)存在編譯亂序與指令執(zhí)行亂序的現(xiàn)象。

?

編譯亂序與指令執(zhí)行亂序,跟本文討論的鎖的意義有何關(guān)系?可以說(shuō),不僅有關(guān)系,還有很大的關(guān)系,關(guān)系到鎖之所以能夠稱之為鎖,能夠用來(lái)保護(hù)共享資源的關(guān)鍵。

?

一個(gè)簡(jiǎn)單的問(wèn)題:在存在編譯亂序與指令執(zhí)行亂序的情況下,怎么保證鎖所保護(hù)的代碼片段,不會(huì)被提前到加鎖之前,或者是放鎖之后執(zhí)行?如果編譯器將鎖保護(hù)下的代碼,通過(guò)編譯優(yōu)化,放到了加鎖之前運(yùn)行?又如果CPU在執(zhí)行指令時(shí),將鎖保護(hù)下的匯編代碼,延遲到了放鎖之后執(zhí)行?如下圖所示:

如上所示,如果編譯器做了它不該做的優(yōu)化,或者CPU做了其不該做的亂序,那么spinlock保護(hù)下的代碼片段,同一時(shí)刻,一定只有一個(gè)線程能夠執(zhí)行的假設(shè)被打破了。此時(shí),雖然spinlock仍舊只能有一個(gè)線程持有,但是spinlock保護(hù)下的代碼,被提到了spinlock保護(hù)之外執(zhí)行,spinlock哪怕功能再?gòu)?qiáng)大,也不能保護(hù)鎖之外的代碼,提取到spinlock鎖之外的代碼,能夠并發(fā)執(zhí)行。

?

但是上面的測(cè)試說(shuō)明,spinlock保護(hù)下的global_count++操作,在多線程下能夠正確執(zhí)行。也就說(shuō)明,無(wú)論是編譯器,還是CPU,并沒(méi)有不合時(shí)宜的做上面的這些優(yōu)化。而分析其原因,剛好引出了鎖(Spinlock、Mutex、RWLock等)的第二層意義:Lock Acquire和Unlock Release。

?

什么是Lock Acquire,Unlock Release又意味著什么?在此之前,需要先看看什么是Acquire和Release。Acquire和Release語(yǔ)義(Semantics)是程序語(yǔ)言和CPU內(nèi)存模型(Memory Model)中的一個(gè)概念。以下,是截取自Preshing博客《Acquire and Release Semantics》一文中,對(duì)Acquire與Release Semantics的定義:

?

Acquire semantics is a property which can only apply to operations which read from shared memory, whether they are read-modify-write operations or plain loads. The operation is then considered a read-acquire. Acquire semantics prevent memory reordering of the read-acquire with any read or write operation which follows it in program order. (注:Acquire語(yǔ)義是一個(gè)作用于內(nèi)存讀操作上的特性,此內(nèi)存讀操作即被視為read-acquire。Acquire語(yǔ)義禁止read-acquire之后所有的內(nèi)存讀寫操作,被提前到read-acquire操作之前進(jìn)行。)

?

Release semantics is a property which can only apply to operations which write to shared memory, whether they are read-modify-write operations or plain stores. The operation is then considered a write-release. Release semantics prevent memory reordering of the write-release with any read or write operation which precedes it in program order.(注:Release語(yǔ)義作用于內(nèi)存寫操作之上的特性,此內(nèi)存寫操作即被視為write-release。Release語(yǔ)義禁止write-release之前所有的內(nèi)存讀寫操作,被推遲到write-release操作之后進(jìn)行。)

?

從Acquire與Release語(yǔ)義的定義可以看出,兩個(gè)語(yǔ)義對(duì)編譯器優(yōu)化、CPU亂序分別做了一個(gè)限制條件:

?

Acquire語(yǔ)義限制了編譯器優(yōu)化、CPU亂序,不能將含有Acquire語(yǔ)義的操作之后的代碼,提到含有Acquire語(yǔ)義的操作代碼之前執(zhí)行;



Release語(yǔ)義限制了編譯器優(yōu)化、CPU亂序,不能將含有Release語(yǔ)義的操作之前的代碼,推遲到含有Release語(yǔ)義的操作代碼之后執(zhí)行;

有了明確的Acquire和Release語(yǔ)義的定義,再回過(guò)頭來(lái)看前面提到的鎖的第二層含義:Lock Acquire和Unlock Release。加鎖操作自帶Acquire語(yǔ)義,解鎖操作自帶Release語(yǔ)義。將加鎖、解鎖的兩個(gè)語(yǔ)義結(jié)合起來(lái),就構(gòu)成了以下的完整的鎖的含義圖:

spinlock,只有帶有了Acquire和Release語(yǔ)義,才算是一個(gè)真正完整可用的鎖——Acquire與Release語(yǔ)義間,構(gòu)成了一個(gè)臨界區(qū)。獲取spinlock后的線程,可以大膽的運(yùn)行全局變量的讀寫,而不必?fù)?dān)心其他并發(fā)線程對(duì)于此變量的并發(fā)訪問(wèn)。

?

好消息是,pthread lib所提供的spinlock、mutex,其加鎖操作都自帶了acquire語(yǔ)義,解鎖操作都自帶了release語(yǔ)義。因此,哪怕我們?cè)谑褂玫倪^(guò)程中,不知道有這兩個(gè)語(yǔ)義的存在,也能夠正確的使用這些鎖。但是,讀者需要實(shí)現(xiàn)自己的spinlock、mutex(注:實(shí)際情況下,確實(shí)有這個(gè)必要,數(shù)據(jù)庫(kù)系統(tǒng)如Oracle/PostgreSQL/InnoDB,都有自己實(shí)現(xiàn)的Spinlock、Mutex等),那么對(duì)于鎖的了解,到這個(gè)層次,是必不可少的。

?

總結(jié)

?

本文,作為 #并發(fā)編程系列# 的開(kāi)篇,首先跟大家分析了鎖(Spinlock、Mutex、RWLock等)所代表的真正意義。首先,這些鎖要么保證同一時(shí)刻只能由一個(gè)線程持有(如:Spinlock、Mutex),要么保證同一時(shí)刻只能有一個(gè)寫鎖(如:RWLock);其次,鎖的加鎖操作帶有Acquire語(yǔ)義,解鎖操作帶有Release語(yǔ)義。通過(guò)這兩個(gè)條件,保證了加鎖/解鎖之間,構(gòu)成了一個(gè)完整的臨界區(qū),全局資源的更新操作,可以在臨界區(qū)內(nèi)完成,而不必?fù)?dān)心并發(fā)讀寫沖突。而這正是并發(fā)程序設(shè)計(jì)的基礎(chǔ):構(gòu)建一個(gè)Data-Race-Free的多線程系統(tǒng)。

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動(dòng)電源

在工業(yè)自動(dòng)化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動(dòng)力設(shè)備,其驅(qū)動(dòng)電源的性能直接關(guān)系到整個(gè)系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動(dòng)勢(shì)抑制與過(guò)流保護(hù)是驅(qū)動(dòng)電源設(shè)計(jì)中至關(guān)重要的兩個(gè)環(huán)節(jié),集成化方案的設(shè)計(jì)成為提升電機(jī)驅(qū)動(dòng)性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動(dòng)電源

LED 驅(qū)動(dòng)電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個(gè)照明設(shè)備的使用壽命。然而,在實(shí)際應(yīng)用中,LED 驅(qū)動(dòng)電源易損壞的問(wèn)題卻十分常見(jiàn),不僅增加了維護(hù)成本,還影響了用戶體驗(yàn)。要解決這一問(wèn)題,需從設(shè)計(jì)、生...

關(guān)鍵字: 驅(qū)動(dòng)電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動(dòng)電源的公式,電感內(nèi)電流波動(dòng)大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計(jì) 驅(qū)動(dòng)電源

電動(dòng)汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動(dòng)汽車的核心技術(shù)之一是電機(jī)驅(qū)動(dòng)控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動(dòng)系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動(dòng)汽車的動(dòng)力性能和...

關(guān)鍵字: 電動(dòng)汽車 新能源 驅(qū)動(dòng)電源

在現(xiàn)代城市建設(shè)中,街道及停車場(chǎng)照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨(dú)特的優(yōu)勢(shì)逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動(dòng)電源 LED

LED通用照明設(shè)計(jì)工程師會(huì)遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動(dòng)電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動(dòng)電源的電磁干擾(EMI)問(wèn)題成為了一個(gè)不可忽視的挑戰(zhàn)。電磁干擾不僅會(huì)影響LED燈具的正常工作,還可能對(duì)周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來(lái)解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動(dòng)電源

開(kāi)關(guān)電源具有效率高的特性,而且開(kāi)關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動(dòng)電源

關(guān)鍵字: LED 驅(qū)動(dòng)電源 開(kāi)關(guān)電源

LED驅(qū)動(dòng)電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動(dòng)LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動(dòng)電源
關(guān)閉