偏向鎖->輕量級(jí)鎖->重量級(jí)鎖
其中輕量級(jí)鎖就是自旋的加鎖過(guò)程。
內(nèi)核鎖:基于內(nèi)核對(duì)象構(gòu)造的鎖機(jī)制,就是通常說(shuō)的內(nèi)核構(gòu)造模式。
優(yōu)點(diǎn):cpu利用最大化。它發(fā)現(xiàn)資源被鎖住,請(qǐng)求就排隊(duì)等候。線程切換到別處干活,直到接受到可用信號(hào),線程再切回來(lái)繼續(xù)處理請(qǐng)求。
缺點(diǎn):托管代碼->用戶模式代碼->內(nèi)核代碼損耗、線程上下文切換損耗。
在鎖的時(shí)間比較短時(shí),系統(tǒng)頻繁忙于休眠、切換,是個(gè)很大的性能損耗。
自旋鎖:原子操作+自循環(huán)。通常說(shuō)的用戶構(gòu)造模式。 線程不休眠,一直循環(huán)嘗試對(duì)資源訪問(wèn),直到可用。
優(yōu)點(diǎn):完美解決內(nèi)核鎖的缺點(diǎn)。
缺點(diǎn):長(zhǎng)時(shí)間一直循環(huán)會(huì)導(dǎo)致cpu的白白浪費(fèi),高并發(fā)競(jìng)爭(zhēng)下、CPU的消耗特別嚴(yán)重。
混合鎖:內(nèi)核鎖+自旋鎖。 混合鎖是先自旋鎖一段時(shí)間或自旋多少次,再轉(zhuǎn)成內(nèi)核鎖。
優(yōu)點(diǎn):內(nèi)核鎖和自旋鎖的折中方案,利用前二者優(yōu)點(diǎn),避免出現(xiàn)極端情況(自旋時(shí)間過(guò)長(zhǎng),內(nèi)核鎖時(shí)間過(guò)短)。
缺點(diǎn): 自旋多少時(shí)間、自旋多少次,這些策略很難把控。
下面摘錄一些關(guān)于C++
當(dāng)中自旋鎖(spin lock)與互斥量(mutex)的比較
自旋鎖是一種非阻塞鎖,也就是說(shuō),如果某線程需要獲取自旋鎖,但該鎖已經(jīng)被其他線程占用時(shí),該線程不會(huì)被掛起,而是在不斷的消耗CPU的時(shí)間,不停的試圖獲取自旋鎖。
互斥量是阻塞鎖,當(dāng)某線程無(wú)法獲取互斥量時(shí),該線程會(huì)被直接掛起,該線程不再消耗CPU時(shí)間,當(dāng)其他線程釋放互斥量后,操作系統(tǒng)會(huì)激活那個(gè)被掛起的線程,讓其投入運(yùn)行。
如果是多核處理器,如果預(yù)計(jì)線程等待鎖的時(shí)間很短,短到比線程兩次上下文切換時(shí)間要少的情況下,使用自旋鎖是劃算的。
如果是多核處理器,如果預(yù)計(jì)線程等待鎖的時(shí)間較長(zhǎng),至少比兩次線程上下文切換的時(shí)間要長(zhǎng),建議使用互斥量。
如果是單核處理器,一般建議不要使用自旋鎖。因?yàn)?,在同一時(shí)間只有一個(gè)線程是處在運(yùn)行狀態(tài),那如果運(yùn)行線程發(fā)現(xiàn)無(wú)法獲取鎖,只能等待解鎖,但因?yàn)樽陨聿粧炱?,所以那個(gè)獲取到鎖的線程沒(méi)有辦法進(jìn)入運(yùn)行狀態(tài),只能等到運(yùn)行線程把操作系統(tǒng)分給它的時(shí)間片用完,才能有機(jī)會(huì)被調(diào)度。這種情況下使用自旋鎖的代價(jià)很高。
如果加鎖的代碼經(jīng)常被調(diào)用,但競(jìng)爭(zhēng)情況很少發(fā)生時(shí),應(yīng)該優(yōu)先考慮使用自旋鎖,自旋鎖的開(kāi)銷比較小,互斥量的開(kāi)銷較大。
說(shuō)的更通俗一點(diǎn):
如果某個(gè)資源在上鎖后,取得鎖的線程需要做較為復(fù)雜的一系列操作(于是需要耗用更多時(shí)鐘周期)、然后才能釋放鎖,那么其它線程就應(yīng)該用非自旋鎖交出執(zhí)行權(quán)。
相反,如果可以通過(guò)算法優(yōu)化,使得取得鎖的線程只需執(zhí)行很少幾條指令就可以釋放鎖,那么其它線程就可以使用自旋鎖(但是,如果程序是在單核處理器上執(zhí)行,應(yīng)無(wú)條件使用非自旋鎖。這是因?yàn)閴焊鶝](méi)有其他CPU資源供其他線程使用,只能等當(dāng)前線程耗盡自己的時(shí)間片)。
事實(shí)上,“盡量降低鎖的持有時(shí)間”是多線程算法設(shè)計(jì)上的重點(diǎn)和難點(diǎn)所在。只要你清楚知道線程持有鎖的時(shí)間(精確到時(shí)鐘周期),自然就知道什么時(shí)候該用什么鎖了。
以 synchronize關(guān)鍵字為例,synchronize會(huì)獲取對(duì)象的內(nèi)置鎖,而鎖競(jìng)爭(zhēng)是 kernal mode下的,會(huì)經(jīng)過(guò) user mode到 kernal mode 的切換,是比較花時(shí)間的。自旋鎖出現(xiàn)的原因是人們發(fā)現(xiàn)大多數(shù)時(shí)候鎖的占用只會(huì)持續(xù)很短的時(shí)間,甚至低于切換到 kernal mode所花的時(shí)間,所以在進(jìn)入 kernal mode前讓線程等待有限的時(shí)間(自適應(yīng)自旋),如果在此時(shí)間內(nèi)能夠獲取到鎖就避免了很多無(wú)謂的時(shí)間,若不能則再進(jìn)入kernal mode競(jìng)爭(zhēng)鎖。
自旋鎖較互斥鎖之類同步機(jī)制的優(yōu)勢(shì)
2.1 休眠與忙循環(huán)
互斥鎖得不到鎖時(shí),線程會(huì)進(jìn)入休眠,這類同步機(jī)制都有一個(gè)共性就是 一旦資源被占用都會(huì)產(chǎn)生任務(wù)切換,任務(wù)切換涉及很多東西的(保存原來(lái)的上下文,按調(diào)度算法選擇新的任務(wù),恢復(fù)新任務(wù)的上下文,還有就是要修改cr3寄存器會(huì)導(dǎo)致cache失效)這些都是需要大量時(shí)間的,因此用互斥之類來(lái)同步一旦涉及到阻塞代價(jià)是十分昂貴的。
一個(gè)互斥鎖來(lái)控制2行代碼的原子操作,這個(gè)時(shí)候一個(gè)CPU正在執(zhí)行這個(gè)代碼,另一個(gè)CPU也要進(jìn)入, 另一個(gè)CPU就會(huì)產(chǎn)生任務(wù)切換。為了短短的兩行代碼 就進(jìn)行任務(wù)切換執(zhí)行大量的代碼,對(duì)系統(tǒng)性能不利,另一個(gè)CPU還不如直接有條件的死循環(huán),等待那個(gè)CPU把那兩行代碼執(zhí)行完。
2.2 自旋過(guò)程
當(dāng)鎖被其他線程占有時(shí),獲取鎖的線程便會(huì)進(jìn)入自旋,不斷檢測(cè)自旋鎖的狀態(tài)。一旦自旋鎖被釋放,線程便結(jié)束自旋,得到自旋鎖的線程便可以執(zhí)行臨界區(qū)的代碼。對(duì)于臨界區(qū)的代碼必須短小,否則其他線程會(huì)一直受到阻塞,這也是要求鎖的持有時(shí)間盡量短的原因!