溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

互斥鎖與自旋鎖有哪些區(qū)別

發(fā)布時(shí)間:2021-10-19 16:48:25 來(lái)源:億速云 閱讀:139 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“互斥鎖與自旋鎖有哪些區(qū)別”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“互斥鎖與自旋鎖有哪些區(qū)別”吧!

1. 互斥鎖和自旋鎖:誰(shuí)更輕松高效?

想知道它們誰(shuí)更高效,要先了解它們?cè)谧鐾患虑榈男袨橛泻尾煌<僭O(shè)有一個(gè)線程加鎖成功,其他線程加鎖自然會(huì)失敗,失敗線程的處理方式如下:

  • 互斥鎖加鎖失敗后,線程釋放CPU,給其他線程;

  • 自旋鎖加鎖失敗后,線程會(huì)忙等待,直到它拿到鎖;

持有互斥鎖的線程在看到鎖已經(jīng)有主了之后,就會(huì)禮貌的退出,等待之后鎖釋放時(shí)自己被系統(tǒng)喚醒;而自旋鎖呢,它居然在反復(fù)的詢問(wèn)鎖使用完了沒有,這實(shí)在是... 我寫個(gè)while循環(huán)反復(fù)爭(zhēng)奪資源,那不就是自旋鎖咯?不會(huì)吧,不會(huì)吧,不會(huì)真的有人用自旋鎖吧?誰(shuí)更輕松高效這不是一目了然嗎?

其實(shí)吧,自旋鎖也沒那么不堪,使用場(chǎng)景還挺多,在很多場(chǎng)合比互斥鎖更好用,我要在本文給自旋鎖洗地。至于怎么洗,那需要詳細(xì)說(shuō)說(shuō)它們各自的原理,工程方面的選擇,還真就是這么神奇。

2. 互斥鎖

互斥鎖是一種「獨(dú)占鎖」,比如當(dāng)線程 A 加鎖成功后,此時(shí)互斥鎖已經(jīng)被線程 A 獨(dú)占了,只要線程 A 沒有釋放手中的鎖,線程 B 加鎖就會(huì)失敗,失敗的線程B于是就會(huì)釋放 CPU 讓給其他線程,既然線程 B 釋放掉了 CPU,自然線程 B 加鎖的代碼就會(huì)被阻塞。

對(duì)于互斥鎖加鎖失敗而阻塞的現(xiàn)象,是由操作系統(tǒng)內(nèi)核實(shí)現(xiàn)的。當(dāng)加鎖失敗時(shí),內(nèi)核會(huì)將線程置為「睡眠」?fàn)顟B(tài),等到鎖被釋放后,內(nèi)核會(huì)在合適的時(shí)機(jī)喚醒線程,當(dāng)這個(gè)線程成功獲取到鎖后,于是就可以繼續(xù)執(zhí)行。如下圖:

互斥鎖與自旋鎖有哪些區(qū)別

互斥鎖加鎖失敗,就會(huì)從用戶態(tài)陷入內(nèi)核態(tài),內(nèi)核幫我們切換線程,這簡(jiǎn)化了互斥鎖使用的難度,但也存在性能開銷。

那這個(gè)開銷成本是什么呢?會(huì)有兩次線程上下文切換的成本

  • 當(dāng)線程加鎖失敗時(shí),內(nèi)核會(huì)把線程的狀態(tài)從「運(yùn)行」?fàn)顟B(tài)設(shè)置為「睡眠」?fàn)顟B(tài),然后把 CPU 切換給其他線程運(yùn)行;

  • 接著,當(dāng)鎖被釋放時(shí),之前「睡眠」?fàn)顟B(tài)的線程會(huì)變?yōu)椤妇途w」?fàn)顟B(tài),然后內(nèi)核會(huì)在合適的時(shí)間,把 CPU 切換給該線程運(yùn)行。

線程的上下文切換的是什么?當(dāng)兩個(gè)線程是屬于同一個(gè)進(jìn)程,因?yàn)樘摂M內(nèi)存是共享的,所以在切換時(shí),虛擬內(nèi)存這些資源就保持不動(dòng),只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù)。

上下文切換需要幾十納秒到幾微秒之間,如果鎖住的代碼執(zhí)行時(shí)間極短(常見情況),那花在兩次上下文切換的時(shí)間就會(huì)遠(yuǎn)多于鎖住代碼的執(zhí)行時(shí)長(zhǎng)。而且,線程的私有數(shù)據(jù)已經(jīng)在CPU的cache上都預(yù)熱好了,這一出一進(jìn),數(shù)據(jù)可能就涼透了,之后反復(fù)的cache miss那可就真的酸爽。所以,鎖住的代碼執(zhí)行只需要幾納秒的話,為啥不持有CPU繼續(xù)自旋等待呢?

3. 互斥鎖的原理

上面的互斥鎖都基于一個(gè)假設(shè): 這鎖小明拿了,其他人都不可能再染指,除非小明不要了。咦! 這是咋做到的?

先考慮單核場(chǎng)景:能不能硬件做一種加鎖的原子操作呢?能! “test and set”指令就是做這個(gè)事情的,因?yàn)樽约菏且粭l硬件指令,最小執(zhí)行單位,絕對(duì)不可能被打斷。有了”test and set"原子指令,單核環(huán)境下,鎖的實(shí)現(xiàn)問(wèn)題得到了圓滿的解決。

那么多核環(huán)境呢?簡(jiǎn)單嘛,還是“test and set”不就得了,這是一條指令,原子的,不會(huì)有問(wèn)題的。真的嗎?單獨(dú)一條指令能夠保證該指令在單個(gè)核上執(zhí)行過(guò)程中不會(huì)被中斷,但是兩個(gè)核同時(shí)執(zhí)行這個(gè)指令呢?再想想,硬件執(zhí)行時(shí)還是得從內(nèi)存中讀取lock,判斷并設(shè)置狀態(tài)到內(nèi)存,貌似這個(gè)過(guò)程也不是那么原子嘛,這可真是套娃啊。那多個(gè)核執(zhí)行怎么辦呢?首先我們得明白這個(gè)地方的關(guān)鍵點(diǎn),關(guān)鍵點(diǎn)是兩個(gè)核會(huì)并行操作內(nèi)存而且從操作內(nèi)存這個(gè)調(diào)度來(lái)看“test and set”不是原子的,需要先讀內(nèi)存然后再寫內(nèi)存,如果我們保證這個(gè)內(nèi)存操作不能并行,那就回歸單核場(chǎng)景了呀!剛好,硬件提供了鎖內(nèi)存總線的機(jī)制,我們?cè)阪i內(nèi)存總線的狀態(tài)下執(zhí)行test and set操作,就能保證同時(shí)只有一個(gè)核來(lái)test and set,從而避免了多核下發(fā)生的問(wèn)題。

在x86 平臺(tái)上,CPU提供了在指令執(zhí)行期間對(duì)總線加鎖 的手段。CPU芯片上有一條引線#HLOCK pin,如果匯編語(yǔ)言的程序中在一條指令前面加上前綴"LOCK" ,經(jīng)過(guò)匯編以后的機(jī)器代碼就使CPU在執(zhí)行這條指令的時(shí)候把#HLOCK pin的電位拉低,持續(xù)到這條指令結(jié)束時(shí)放開,從而把總線鎖住,這樣同一總線上別的CPU就暫時(shí)不能通過(guò)總線訪問(wèn)內(nèi)存了,保證了這條指令在多處理器環(huán)境中的原子性。

能夠和 LOCK 指令前綴一起使用的指令如下所示:

BT, BTS, BTR, BTC (mem, reg/imm) XCHG, XADD (reg, mem / mem, reg) ADD, OR, ADC, SBB (mem, reg/imm) AND, SUB, XOR (mem, reg/imm) NOT, NEG, INC, DEC (mem)

4. 自旋鎖

自旋鎖是最比較簡(jiǎn)單的一種鎖,一直自旋,利用 CPU 周期,直到鎖可用。需要注意,在單核 CPU 上,需要搶占式的調(diào)度器(即通過(guò)時(shí)鐘中斷一個(gè)線程,運(yùn)行其他線程)。否則,自旋鎖在單 CPU 上無(wú)法使用,因?yàn)橐粋€(gè)自旋的線程永遠(yuǎn)不會(huì)放棄 CPU。

自旋鎖開銷少,在多核系統(tǒng)下一般不會(huì)主動(dòng)產(chǎn)生線程切換,適合異步、協(xié)程等在用戶態(tài)切換請(qǐng)求的編程方式,但如果被鎖住的代碼執(zhí)行時(shí)間過(guò)長(zhǎng),自旋的線程會(huì)長(zhǎng)時(shí)間占用 CPU 資源,所以自旋的時(shí)間和被鎖住的代碼執(zhí)行的時(shí)間是成「正比」的關(guān)系,我們需要清楚的知道這一點(diǎn)。

自旋鎖與互斥鎖使用層面比較相似,但實(shí)現(xiàn)層面上完全不同:當(dāng)加鎖失敗時(shí),互斥鎖用「線程切換」來(lái)應(yīng)對(duì),自旋鎖則用「忙等待」來(lái)應(yīng)對(duì)。這里的忙等待,可以用「while」循環(huán)實(shí)現(xiàn),但最好不要這么干??!CPU提供了「PAUSE」指令來(lái)實(shí)現(xiàn)忙等待。

互斥鎖與自旋鎖有哪些區(qū)別

5. 自旋鎖原理

自旋鎖不就是不停的while循環(huán)去獲取鎖,還需要講原理?等等,去獲取鎖狀態(tài)的時(shí)候怎么保證數(shù)據(jù)原子性?難道又用互斥鎖?如果真套一層互斥鎖,那我就給自旋鎖洗不了地了。顯然在這里不能這么套娃!

反復(fù)嘗試加鎖的時(shí)候,包含兩個(gè)步驟:

  • 第一步,查看鎖的狀態(tài),如果鎖是空閑的,則執(zhí)行第二步;

  • 第二步,將鎖設(shè)置為當(dāng)前線程持有;

這個(gè)過(guò)程叫做「Compare And Swap」,簡(jiǎn)稱「CAS」,它把上述兩個(gè)步驟合并成一條硬件級(jí)指令,在「用戶態(tài)」完成加鎖和解鎖操作,不會(huì)主動(dòng)產(chǎn)生線程上下文切換,所以相比互斥鎖來(lái)說(shuō),會(huì)快一些,開銷也小一些。

上面說(shuō),不推薦while循環(huán)獲取鎖,Intel CPU提供的「PAUSE」指令,「PAUSE」指令是什么?那它如何解決無(wú)腦while循環(huán)占用CPU且低效率的問(wèn)題呢?

其實(shí)自旋鎖不會(huì)主動(dòng)釋放CPU,所以不可能解決占用CPU的問(wèn)題,但能讓這個(gè)過(guò)程更省電,搶占鎖效率更高。

「PAUSE」指令通過(guò)讓CPU休息一定的時(shí)鐘周期,在此休息期間,耗電幾乎停滯。休息的時(shí)鐘周期,不同版本CPU不一樣,大概在幾十到上百時(shí)鐘周期之間。以5Ghz主頻運(yùn)行的CPU為例,一個(gè)時(shí)鐘周期就是0.2納秒。

休息的時(shí)鐘周期不是越大越好。比如Intel新一代的Skylake架構(gòu)中,初期「PAUSE」指令的休息周期高達(dá)140個(gè)時(shí)鐘周期。這直接導(dǎo)致MySQL在理論上性能更好的CPU上,數(shù)據(jù)庫(kù)性能跑出了比前幾年CPU更糟糕的成績(jī),擠出的牙膏吸回去了!在隨后的步進(jìn)中降低了「PAUSE」的時(shí)鐘周期到上一代的10個(gè)時(shí)鐘周期,數(shù)據(jù)庫(kù)展現(xiàn)的性能才恢復(fù)了牙膏廠該有的水準(zhǔn)(每代性能提升一丟丟)。

另一個(gè)優(yōu)點(diǎn)跟流水線有關(guān)系,頻繁的檢測(cè)會(huì)讓流水線上充滿了讀操作。另外一個(gè)線程往流水線上丟入一個(gè)鎖變量寫操作的時(shí)候,必須對(duì)流水線進(jìn)行重排,因?yàn)镃PU必須保證所有讀操作讀到正確的值。流水線重排十分耗時(shí),影響lock()的性能。設(shè)想一下,當(dāng)一個(gè)獲得鎖的工作線程W從臨界區(qū)退出,在調(diào)用unlock釋放鎖的時(shí)候,有若干個(gè)等待線程S都在自旋檢測(cè)鎖是否可用,此時(shí)W線程會(huì)產(chǎn)生一個(gè)store指令,若干個(gè)S線程會(huì)產(chǎn)生很多l(xiāng)oad指令,在store之后的load指令要等待store在流水線上執(zhí)行完畢才能執(zhí)行,由于處理器是亂序執(zhí)行,在沒有store指令之前,處理器對(duì)多個(gè)沒有依賴的load是可以隨機(jī)亂序執(zhí)行的,當(dāng)有了store指令之后,需要reorder重新排序執(zhí)行,此時(shí)會(huì)嚴(yán)重影響處理器性能,按照intel的說(shuō)法,會(huì)帶來(lái)25倍的性能損失。Pause指令的作用就是減少并行l(wèi)oad的數(shù)量,從而減少reorder時(shí)所耗時(shí)間。

感謝各位的閱讀,以上就是“互斥鎖與自旋鎖有哪些區(qū)別”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)互斥鎖與自旋鎖有哪些區(qū)別這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI