之前一直沒(méi)有怎么關(guān)注過(guò)這個(gè)問(wèn)題,前些日子在面試一家公司的時(shí)候,,面試官提到了pthread_cond_wait/pthread_cond_signal的實(shí)現(xiàn),,當(dāng)時(shí)答的不是很好,,回來(lái)就查了nptl的代碼,。前天,,水木上又有人問(wèn)到了信號(hào)量和互斥鎖的問(wèn)題,,我想還是對(duì)它們的區(qū)別與實(shí)現(xiàn)總結(jié)一下,。 首先了解一些信號(hào)量和線程互斥鎖的語(yǔ)義上的區(qū)別: >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 援引CU上一篇帖子的內(nèi)容: “信號(hào)量用在多線程多任務(wù)同步的,一個(gè)線程完成了某一個(gè)動(dòng)作就通過(guò)信號(hào)量告訴別的線程,,別的線程再進(jìn)行某些動(dòng)作(大家都在sem_wait的時(shí)候,,就阻塞在那里)。而互斥鎖是用在多線程多任務(wù)互斥的,,一個(gè)線程占用了某一個(gè)資源,,那么別的線程就無(wú)法訪問(wèn),直到這個(gè)線程unlock,,其他的線程才開始可以利用這個(gè)資源,。比如對(duì)全局變量的訪問(wèn),有時(shí)要加鎖,,操作完了,,在解鎖。有的時(shí)候鎖和信號(hào)量會(huì)同時(shí)使用的” 也就是說(shuō),,信號(hào)量不一定是鎖定某一個(gè)資源,,而是流程上的概念,比如:有A,B兩個(gè)線程,,B線程要等A線程完成某一任務(wù)以后再進(jìn)行自己下面的步驟,,這個(gè)任務(wù)并不一定是鎖定某一資源,還可以是進(jìn)行一些計(jì)算或者數(shù)據(jù)處理之類,。而線程互斥量則是“鎖住某一資源”的概念,,在鎖定期間內(nèi),其他線程無(wú)法對(duì)被保護(hù)的數(shù)據(jù)進(jìn)行操作,。在有些情況下兩者可以互換,。 兩者之間的區(qū)別: 作用域 信號(hào)量: 進(jìn)程間或線程間(linux僅線程間) 互斥鎖: 線程間 上鎖時(shí) 信號(hào)量: 只要信號(hào)量的value大于0,其他線程就可以sem_wait成功,,成功后信號(hào)量的value減一,。若value值不大于0,則sem_wait阻塞,,直到sem_post釋放后value值加一,。一句話,信號(hào)量的value>=0,。 互斥鎖: 只要被鎖住,,其他任何線程都不可以訪問(wèn)被保護(hù)的資源,。如果沒(méi)有鎖,獲得資源成功,,否則進(jìn)行阻塞等待資源可用,。一句話,線程互斥鎖的vlaue可以為負(fù)數(shù),。 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 接下來(lái),,我們需要分析一下信號(hào)量和線程互斥鎖的實(shí)現(xiàn)機(jī)制。 在Linux下,,信號(hào)量和線程互斥鎖的實(shí)現(xiàn)都是通過(guò)futex系統(tǒng)調(diào)用,。 futex(快速用戶區(qū)互斥的簡(jiǎn)稱)是一個(gè)在Linux上實(shí)現(xiàn)鎖定和構(gòu)建高級(jí)抽象鎖如信號(hào)量和POSIX互斥的基本工具。它們第一次出現(xiàn)在內(nèi)核開發(fā)的2.5.7版,;其語(yǔ)義在2.5.40固定下來(lái),,然后在2.6.x系列穩(wěn)定版內(nèi)核中出現(xiàn)。 Futex 是fast userspace mutex的縮寫,,意思是快速用戶空間互斥體,。Linux內(nèi)核把它們作為快速的用戶空間的鎖和信號(hào)量的預(yù)制構(gòu)件提供給開發(fā)者。Futex非?;A(chǔ),,借助其自身的優(yōu)異性能,構(gòu)建更高級(jí)別的鎖的抽象,,如POSIX互斥體,。大多數(shù)程序員并不需要直接使用Futex,它一般用來(lái)實(shí)現(xiàn)像NPTL這樣的系統(tǒng)庫(kù),。 Futex 由一塊能夠被多個(gè)進(jìn)程共享的內(nèi)存空間(一個(gè)對(duì)齊后的整型變量)組成,;這個(gè)整型變量的值能夠通過(guò)匯編語(yǔ)言調(diào)用CPU提供的原子操作指令來(lái)增加或減少,并且一個(gè)進(jìn)程可以等待直到那個(gè)值變成正數(shù),。Futex 的操作幾乎全部在應(yīng)用程序空間完成,;只有當(dāng)操作結(jié)果不一致從而需要仲裁時(shí),才需要進(jìn)入操作系統(tǒng)內(nèi)核空間執(zhí)行,。這種機(jī)制允許使用 futex 的鎖定原語(yǔ)有非常高的執(zhí)行效率:由于絕大多數(shù)的操作并不需要在多個(gè)進(jìn)程之間進(jìn)行仲裁,,所以絕大多數(shù)操作都可以在應(yīng)用程序空間執(zhí)行,而不需要使用(相對(duì)高代價(jià)的)內(nèi)核系統(tǒng)調(diào)用,。 ---------------------------------------------------------------- 插播一段關(guān)于x86原子操作指令的說(shuō)明: cmpxchg 比較交換指令,,其語(yǔ)義為:
Intel白皮書上的說(shuō)明如下: (* Accumulator = AL, AX, EAX, or RAX depending on whether a byte, word, doubleword, or quadword comparison is being performed *) IF accumulator = DEST THEN ZF ← 1; DEST ← SRC; ELSE ZF ← 0; accumulator ← DEST; FI; 使用此原子操作可以實(shí)現(xiàn)自旋鎖,之前有一篇文章中描述了實(shí)現(xiàn):
關(guān)于smp下的原子操作的一些說(shuō)明: 原子操作是不可分割的,,在執(zhí)行完畢不會(huì)被任何其它任務(wù)或事件中斷,。在單處理器系統(tǒng)(UniProcessor)中,能夠在單條指令中完成的操作都可以認(rèn)為是" 原子操作",因?yàn)橹袛嘀荒馨l(fā)生于指令之間,。這也是某些CPU指令系統(tǒng)中引入了test_and_set,、test_and_clear等指令用于臨界資源互斥的原因。在對(duì)稱多處理器(Symmetric Multi-Processor)結(jié)構(gòu)中就不同了,,由于系統(tǒng)中有多個(gè)處理器在獨(dú)立地運(yù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)境中的原子性,。 當(dāng)然,,并不是所有的指令前面都可以加lock前綴的,只有ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG,DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, 和 XCHG指令前面可以加lock指令,,實(shí)現(xiàn)原子操作,。 ---------------------------------------------------------------- 廣告回來(lái)了,我們繼續(xù),。 futex保存在用戶空間的共享內(nèi)存中,,并且通過(guò)原子操作進(jìn)行操作。在大部分情況下,,資源不存在爭(zhēng)用的情況下,,進(jìn)程或者線程可以立刻獲得資源成功,實(shí)際上就沒(méi)有必要調(diào)用系統(tǒng)調(diào)用,,陷入內(nèi)核了,。實(shí)際上,futex的作用就在于減少系統(tǒng)調(diào)用的次數(shù),,來(lái)提高系統(tǒng)的性能,。 線程互斥鎖pthread_mutex_t的實(shí)現(xiàn)原理:
信號(hào)量sem_t的實(shí)現(xiàn)原理(直接從glibc/nptl/DESIGN-sem.txt中摘的):
對(duì)比,pthread_mutex_unlock()和sem_post()的實(shí)現(xiàn),,我們發(fā)現(xiàn)一個(gè)不同點(diǎn),,sem_post()無(wú)論如何都會(huì)調(diào)用futex_wake(),進(jìn)行系統(tǒng)調(diào)用,。但是pthread_mutex_unlock()卻符合futex的初衷,,只有在需要仲裁的時(shí)候才調(diào)用futex_wake()。那么什么是仲裁條件呢,? 前面說(shuō)過(guò)信號(hào)量和線程互斥鎖語(yǔ)義上的區(qū)別在于信號(hào)量的value>=0,,而線程互斥鎖的value可以為負(fù)數(shù),。 對(duì)于lock操作,這兩個(gè)倒是沒(méi)有多少差別,。信號(hào)量只要value>0就可以獲得資源,,線程互斥鎖需要value=1。 但是對(duì)于unlock操作,,這兩個(gè)就有一些差別了,。信號(hào)量和線程互斥鎖,都會(huì)增加對(duì)應(yīng)的value,。如果加1后,,value為1,對(duì)于線程互斥鎖來(lái)講,,實(shí)際上表明資源可用,,并且之前沒(méi)有其他的線程在等待這個(gè)資源;否則說(shuō)明還有其他線程在等待這個(gè)資源,,需要調(diào)用futex系統(tǒng)調(diào)用喚醒它們,。但是對(duì)于信號(hào)量,由于value必須>=0,。那么加1后,,即使value為1,也無(wú)法判定現(xiàn)在沒(méi)有其他的進(jìn)程或線程正在等待資源,,所以必須調(diào)用futex系統(tǒng)調(diào)用,。例如:
感興趣的同學(xué)可以使用strace跟蹤一下,,進(jìn)行驗(yàn)證。要注意忽略程序運(yùn)行初始化的那個(gè)futex_wake ;-) |
|