為了接口的將來(lái)的擴(kuò)展性,可以將bar()函數(shù)用同樣方法拆成bar_withou_lock()函數(shù)和bar()函數(shù),。 在Douglas C. Schmidt(ACE框架的主要編寫者)的“Strategized Locking, Thread-safe Interface, and Scoped Locking”論文中,,提出了一個(gè)基于C++的線程安全接口模式(Thread-safe interface pattern),,與AUPE的方法有異曲同工之妙,。即在設(shè)計(jì)接口的時(shí)候,,每個(gè)函數(shù)也被拆成兩個(gè)函數(shù),,沒有使用鎖的函數(shù)是private或者protected類型,使用鎖的的函數(shù)是public類型,。接口如下:
作為對(duì)外接口的public函數(shù)只能調(diào)用無(wú)鎖的私有變量函數(shù),,而不能互相調(diào)用。在函數(shù)具體實(shí)現(xiàn)上,,這兩種方法基本是一樣的,。 上面講的兩種方法在通常情況下是沒問(wèn)題的,可以有效的避免死鎖,。但是有些復(fù)雜的回調(diào)情況下,,則必須使用遞歸鎖。比如foo函數(shù)調(diào)用了外部庫(kù)的函數(shù),,而外部庫(kù)的函數(shù)又回調(diào)了bar()函數(shù),,此時(shí)必須使用遞歸鎖,否則仍然會(huì)死鎖,。AUPE 一書在第十二章就舉了一個(gè)必須使用遞歸鎖的程序例子,。 1.3 讀寫鎖的遞歸性
讀寫鎖(例如Linux中的pthread_rwlock_t)提供了一個(gè)比互斥鎖更高級(jí)別的并發(fā)訪問(wèn)。讀寫鎖的實(shí)現(xiàn)往往是比互斥鎖要復(fù)雜的,,因此開銷通常也大于互斥鎖,。在我的Linux機(jī)器上實(shí)驗(yàn)發(fā)現(xiàn),單純的寫鎖的時(shí)間開銷差不多是互斥鎖十倍左右,。 在系統(tǒng)不支持讀寫鎖時(shí),,有時(shí)需要自己來(lái)實(shí)現(xiàn),通常是用條件變量加讀寫計(jì)數(shù)器實(shí)現(xiàn)的,。有時(shí)可以根據(jù)實(shí)際情況,,實(shí)現(xiàn)讀者優(yōu)先或者寫者優(yōu)先的讀寫鎖。 讀寫鎖的優(yōu)勢(shì)往往展現(xiàn)在讀操作很頻繁,,而寫操作較少的情況下,。如果寫操作的次數(shù)多于讀操作,并且寫操作的時(shí)間都很短,,則程序很大部分的開銷都花在了讀寫鎖上,,這時(shí)反而用互斥鎖效率會(huì)更高些。 相信很多同學(xué)學(xué)習(xí)了讀寫鎖的基本使用方法后,,都寫過(guò)下面這樣的程序(Linux下實(shí)現(xiàn)),。
你會(huì)很疑惑的發(fā)現(xiàn),,程序1先加讀鎖,,后加寫鎖,按理來(lái)說(shuō)應(yīng)該阻塞,,但程序卻能順利執(zhí)行,。而程序2卻發(fā)生了阻塞,。 更近一步,你能說(shuō)出執(zhí)行下面的程序3和程序4會(huì)發(fā)生什么嗎,?
在POSIX標(biāo)準(zhǔn)中,,如果一個(gè)線程先獲得寫鎖,又獲得讀鎖,,則結(jié)果是無(wú)法預(yù)測(cè)的,。這就是為什么程序1的運(yùn)行出人所料。需要注意的是,,讀鎖是遞歸鎖(即可重入),,寫鎖是非遞歸鎖(即不可重入)。因此程序3不會(huì)死鎖,,而程序4會(huì)一直阻塞,。 讀寫鎖是否可以遞歸會(huì)可能隨著平臺(tái)的不同而不同,因此為了避免混淆,,建議在不清楚的情況下盡量避免在同一個(gè)線程下混用讀鎖和寫鎖,。 在系統(tǒng)不支持遞歸鎖,而又必須要使用時(shí),就需要自己構(gòu)造一個(gè)遞歸鎖,。通常,,遞歸鎖是在非遞歸互斥鎖加引用計(jì)數(shù)器來(lái)實(shí)現(xiàn)的。簡(jiǎn)單的說(shuō),,在加鎖前,,先判斷上一個(gè)加鎖的線程和當(dāng)前加鎖的線程是否為同一個(gè)。如果是同一個(gè)線程,,則僅僅引用計(jì)數(shù)器加1,。如果不是的話,則引用計(jì)數(shù)器設(shè)為1,,則記錄當(dāng)前線程號(hào),,并加鎖。一個(gè)例子可以看這里,。需要注意的是,,如果自己想寫一個(gè)遞歸鎖作為公用庫(kù)使用,就需要考慮更多的異常情況和錯(cuò)誤處理,,讓代碼更健壯一些,。 |
|