[臨界區(qū)和競(jìng)爭(zhēng)條件]
所謂臨界區(qū)就是訪問和操作共享數(shù)據(jù)的代碼段。多個(gè)執(zhí)行線程并發(fā)訪問同一個(gè)資源通常是不安全的,為了避免在臨界區(qū)中并發(fā)訪問,coder必須保證這些代碼原子執(zhí)行。
如果兩個(gè)執(zhí)行線程有可能處于同一個(gè)臨界區(qū)中同時(shí)執(zhí)行,那么這就是程序包含的一個(gè)bug。如果這種情況確實(shí)發(fā)生了,我們就稱它是競(jìng)爭(zhēng)條件(race conditions)。避免并發(fā)和防止競(jìng)爭(zhēng)條件稱為同步(synchronization)。
?
[造成并發(fā)執(zhí)行的原因]
用戶空間之所以需要同步,是因?yàn)?span>用戶程序會(huì)被調(diào)度程序搶占和重新調(diào)度。在內(nèi)核中有類似可能造成并發(fā)執(zhí)行的原因:
中斷:中斷幾乎可以在任何時(shí)刻異步發(fā)生,也就是隨時(shí)打斷當(dāng)前正在執(zhí)行的代碼;
軟中斷和tasklet:內(nèi)核能在任何時(shí)刻喚醒或調(diào)度軟中斷和tasklet,打斷當(dāng)前正在執(zhí)行的代碼;
內(nèi)核搶占:因?yàn)閮?nèi)核具有搶占性,所以內(nèi)核中的任務(wù)可能會(huì)被另一任務(wù)搶占;
睡眠及與用戶空間的同步:在內(nèi)核執(zhí)行的進(jìn)程可能會(huì)睡眠,這就會(huì)喚醒調(diào)度程序,從而導(dǎo)致調(diào)度一個(gè)新的用戶進(jìn)程執(zhí)行;
對(duì)稱處理器:兩個(gè)或多個(gè)處理器可以同時(shí)執(zhí)行代碼。
?
[哪些代碼需要同步]
我們?cè)诰帉憙?nèi)核代碼時(shí),你要問自己下面這些問題:
這個(gè)數(shù)據(jù)是不是全局的?除了當(dāng)前線程外,其他線程能不能訪問它?
這個(gè)數(shù)據(jù)會(huì)不會(huì)在進(jìn)程上下文和中斷上下文種共享?它是不是要在兩個(gè)不同的中斷處理程序中共享?
進(jìn)程在訪問數(shù)據(jù)時(shí)可不可能被搶占?被調(diào)度的新程序會(huì)不會(huì)訪問同一數(shù)據(jù)?
當(dāng)前進(jìn)程是不是會(huì)睡眠(阻塞)在某些資源上,如果是,它會(huì)讓共享數(shù)據(jù)處于何種狀態(tài)?
怎樣防止數(shù)據(jù)失控?
如果這個(gè)函數(shù)又在另一個(gè)處理上被調(diào)度將會(huì)發(fā)生什么呢?
如何確保代碼遠(yuǎn)離并發(fā)威脅呢?
?
簡(jiǎn)而言之,幾乎訪問所有的內(nèi)核全局變量和共享數(shù)據(jù)都需要某種形式的同步方法。
?
[死鎖]
死鎖的產(chǎn)生需要一定的條件:要一個(gè)或多個(gè)執(zhí)行線程和一個(gè)或多個(gè)資源,每個(gè)線程都在等待其中的一個(gè)資源,但所有的資源都已經(jīng)被占用了。所有線程都在相互等待,但它們永遠(yuǎn)不會(huì)釋放已經(jīng)占有的資源,于是任何資源都無(wú)法繼續(xù),這就意味著死鎖的發(fā)生。
Example:有兩個(gè)線程和兩把鎖
線程1????????????????? 線程2
獲得鎖A??????????????? 獲得鎖B
試圖獲得鎖B??????????? 試圖獲得鎖A
等待鎖B??????????????? 等待鎖A
?
[原子操作]
原子操作可以保證指令以原子的方式執(zhí)行-執(zhí)行過程不被打斷。內(nèi)核提供了兩組原子操作接口:一組針對(duì)整數(shù)進(jìn)行操作,另一組針對(duì)單獨(dú)的位進(jìn)行操作。
原子整數(shù)類型
?
?
[自旋鎖]
自旋鎖(spin lock)最多只能被一個(gè)可執(zhí)行線程持有。如果一個(gè)執(zhí)行線程試圖獲得一個(gè)被已經(jīng)持有(即所謂的爭(zhēng)用)的自旋鎖,那么該線程就會(huì)一直進(jìn)行忙循環(huán)-旋轉(zhuǎn)-等待鎖重新可用。
spinlock結(jié)構(gòu)體:
?
?
一個(gè)被爭(zhēng)用的自旋鎖是的請(qǐng)求它的線程在等待鎖重新可用時(shí)自旋,特別浪費(fèi)處理器時(shí)間,這種行為是自旋鎖的要點(diǎn)。所以自旋鎖不應(yīng)該被長(zhǎng)時(shí)間持有。持有自旋鎖的時(shí)間最好小于完成兩次上下文切換的耗時(shí),也就是兩次調(diào)用schedule()的時(shí)間。
自旋鎖可以用于中斷處理程序中,但是信號(hào)量不可以,信號(hào)量會(huì)導(dǎo)致睡眠。
使用鎖的時(shí)候一定要對(duì)癥下藥,要有針對(duì)性。要知道需要保護(hù)的是數(shù)據(jù)而不是代碼。
?
[信號(hào)量]
Linux中的信號(hào)量是一種睡眠鎖。如果有一個(gè)任務(wù)試圖獲得一個(gè)不可用(已經(jīng)被占用)的信號(hào)量時(shí),信號(hào)量會(huì)將其推進(jìn)一個(gè)等待隊(duì)列中,然后讓其睡眠。這時(shí)處理器能重獲自由,從而去執(zhí)行其他代碼。當(dāng)持有的信號(hào)量可用(被釋放)后,處于等待列隊(duì)中的那個(gè)任務(wù)將被喚醒,并獲得該信號(hào)量。
?semaphore結(jié)構(gòu)體:
?
?
使用信號(hào)量應(yīng)注意的地方:
由于爭(zhēng)用信號(hào)量的進(jìn)程在等待鎖重新變?yōu)榭捎脮r(shí)會(huì)睡眠,所以信號(hào)量適合用于鎖會(huì)被長(zhǎng)時(shí)間持有的情況;
相反,鎖被短時(shí)間持有時(shí),使用信號(hào)量就不能太適合了。因?yàn)樗摺⒕S護(hù)等待隊(duì)列以及喚醒所花費(fèi)的開銷可能比鎖被占用的全部時(shí)間還要長(zhǎng);
由于執(zhí)行線程在鎖被爭(zhēng)用時(shí)會(huì)睡眠,所以只能在進(jìn)程上下文中才能獲取信號(hào)量鎖,因?yàn)樵谥袛嗌舷挛氖遣荒苓M(jìn)行調(diào)度的;
你可以在持有信號(hào)量時(shí)去睡眠,因?yàn)楫?dāng)其他進(jìn)程試圖獲得同一信號(hào)量時(shí)不會(huì)因此而死鎖。
在你占用信號(hào)量的同時(shí)不能占用自旋鎖。因?yàn)樵谀愕却盘?hào)量時(shí)可能會(huì)睡眠,而在持有自旋鎖時(shí)是不允許睡眠的。
?
[互斥體]
Linux最新的linux內(nèi)核中,互斥體mutex是一種實(shí)現(xiàn)互斥的特定睡眠鎖。Mutex在內(nèi)核中對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)mutex,其行為和使用計(jì)數(shù)為1的信號(hào)量類似,但操作接口更簡(jiǎn)單,實(shí)現(xiàn)也更高效,而且使用限制更強(qiáng)。
?mutex結(jié)構(gòu)體:
?
?
Mutex使用限制:
任何時(shí)候中只有一個(gè)任務(wù)可以持有mutex,也就是說(shuō),mutex的使用計(jì)數(shù)永遠(yuǎn)是1;
給mutex上鎖者必須負(fù)責(zé)給其再解鎖,你不能在一個(gè)上下文中鎖定一個(gè)mutex,而在另一個(gè)上下文中給它解鎖。這個(gè)限制使得mutex不合適內(nèi)核同用戶空間復(fù)雜的同步場(chǎng)景。最常使用的方式是:在同一上下文中上鎖和解鎖。
遞歸地上鎖和解鎖是不允許的。也就是說(shuō),你不能遞歸地持有同一個(gè)鎖,同樣你也不能再去解鎖一個(gè)已經(jīng)被解開的mutex;
Mutex不能在中斷或者下半部中使用;
Mutex只能通過官方API管理
?
[信號(hào)量和互斥體]
互斥鎖和信號(hào)量很相似,內(nèi)核中兩者共存會(huì)令人混淆。所幸,它們的標(biāo)準(zhǔn)使用方式都有很簡(jiǎn)單的規(guī)范:除非mutex的某個(gè)約束妨礙你使用,否則相比信號(hào)量要優(yōu)先使用mutex。當(dāng)你寫新代碼時(shí),只有碰到特殊場(chǎng)合才會(huì)需要使用信號(hào)量。因此建議首選mutex。
?
[自旋鎖和互斥體]
了解何時(shí)使用自旋鎖,何時(shí)使用互斥體或者信號(hào)量對(duì)編寫優(yōu)良代碼很重要,但是多數(shù)情況下,并不需要太多的考慮,因?yàn)樵?span>中斷上下文中只能使用自旋鎖,而在任務(wù)睡眠時(shí)只能使用互斥體。
需求
建議加鎖方法
低開銷加鎖
優(yōu)先使用自旋鎖
短期鎖定
優(yōu)先使用自旋鎖
長(zhǎng)期加鎖
優(yōu)先使用互斥體
中斷上下文中加鎖
使用自旋鎖
下半部加鎖
使用自旋鎖
持有鎖需要睡眠
使用互斥體
?
評(píng)論
查看更多