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

背景

?

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

?

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

?

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

?


一個測試用例

?

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

此用例中,N個線程,并發(fā)更新一個全局變量。讓我們先來看一個簡單的測試,全局變量global_count沒有任何保護(hù),此時會發(fā)生什么?

?

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

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

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

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

?

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

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

?

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

?

鎖的意義

?

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

?

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

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

?

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

?

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

?

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

?

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

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

?

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

?

什么是Lock Acquire,Unlock Release又意味著什么?在此之前,需要先看看什么是Acquire和Release。Acquire和Release語義(Semantics)是程序語言和CPU內(nèi)存模型(Memory Model)中的一個概念。以下,是截取自Preshing博客《Acquire and Release Semantics》一文中,對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語義是一個作用于內(nèi)存讀操作上的特性,此內(nèi)存讀操作即被視為read-acquire。Acquire語義禁止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語義作用于內(nèi)存寫操作之上的特性,此內(nèi)存寫操作即被視為write-release。Release語義禁止write-release之前所有的內(nèi)存讀寫操作,被推遲到write-release操作之后進(jìn)行。)

?

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

?

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



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

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

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

?

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

?

總結(jié)

?

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

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

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉