版權聲明:您好,轉載請留下本人部落格的位址,謝謝 https://blog.csdn.net/hongbochen1223/article/details/48318965
(一):臨界區和競争條件
所謂臨界區(也稱為臨界段)就是通路和操作共享資料的代碼段.多個執行線程并發通路同一個資源通常是不安全的,為了避免在臨界區中并發通路,程式設計者必須保證這些代碼原子的執行--也就是說,操作在執行結束前不能被打斷,就如同整個臨界區是一個不可分割的指令一樣.如果兩個執行線程有可能處于同一個臨界區中同時執行,那麼這就是程式包含的一個bug.如果這種情況确實發生了,我們就成他是競争條件,這樣命名是因為這裡會存線上程競争.避免并發和防止競争條件稱為同步.
1:單個變量
現在,首先看一個特殊計算的例子.考慮一個非常簡單的共享資源:一個全局整型變量和一個簡單的臨界區.其中的操作僅僅是将整型變量的值增加1.
i++;
該操作可以轉換成類似于下面動作的機器指令序列:
得到目前變量i 的值并且拷貝到一個寄存器中
将寄存器中的值加1
把i的新值寫會到記憶體中
現在假定有兩個執行線程同時進入臨界區,如果i的初始值是7,那麼我們所期望的結果應該像下面這樣(每一行代表一個時間單元):
線程1 線程2
擷取i(7) --------
增加i(7->8) --------
寫回i(8) --------
--------- 獲得i(8)
--------- 增加i(8->9)
--------- 寫回i(9)
這是我們所期望的,但是實際上的運作情況可能是這樣的:
線程1 線程2
獲得i(7) 獲得i(7)
增加i(7->8) ---------
------------- 增加i(7->8)
寫回i(8) -----------
------------ 寫回i(8)
這樣就不是我們希望出現的結果了,但是實際情況中這種現象也是可能會發生的.那麼這種情況解決起來也是相對比較簡單的---我們僅僅需要将這些指令作為一個不可分割的整體來執行就可以了.多數處理器都提供了指令來原子的讀變量,增加變量,然後寫回變量,使用這樣的指令就能解決一些問題.使用這個指令唯一的可能結果為:
線程1 線程2
增加i(7->8) ---------
-------------- 增加i(8->9)
或者是相反
線程1 線程2
-------- 增加i(7->8)
增加i(8->9) ---------
兩個原子操作執行根本就不可能發生,因為處理器會從實體上確定這種不可能.
(二):加鎖
鎖有多種多樣的形式,而且加鎖的粒度也各不相同---Linux自身實作了幾種不同的鎖機制.各種鎖機制之間的差別主要在于:當鎖已經被其他線程持有,因而不可用時的行為表現--一些鎖被争用時會簡單的執行忙等待,而另外一些鎖會使目前任務睡眠知道鎖可用為止.
1:造成并發執行的原因
使用者空間之是以需要同步,是因為使用者程式會被排程程式搶占和重新排程.由于使用者程序可能在任何時刻被搶占,而排程程式完全可能選擇另一個高優先級的程序到處理器上執行,是以就會使得一個程式正處于臨界區的時候,被非自願地搶占了.如果新排程的程序随後也進入同一個臨界區(比如說這兩個程序要操作共享的記憶體,或者向同一個檔案描述符寫入),前後兩個程序互相之間就會産生競争.另外,因為信号處理是異步發生的,是以,即使是單線程的多個程序共享檔案,或者是在同一程式内部處理信号,也有可能産生競争條件.這種類型的并發操作--這裡其實兩者并不真是同時發生的,但他們互相交叉進行,是以也被稱作僞并發執行.
如果有一台支援對稱多處理器的機器,那麼兩個程序就可以真正的在臨界區中同時執行,這種類型稱為真并發.
核心中有類似可能造成并發執行的原因.他們是:
中斷--中斷幾乎可以在任何時刻異步發生,也就可能随時打斷目前正在執行的代碼
軟中斷和tasklet--核心能在任何時刻喚醒或排程軟中斷和tasklet,打斷目前正在執行的代碼
核心搶占--因為核心具有搶占性,是以核心中的任務可能會被另一個任務搶占
睡眠及使用者空間的同步--在核心執行的程序可能會睡眠,這就會喚醒排程程式,進而導緻排程一個新的使用者程序執行
對稱多處理器--兩個或多個處理器可以同時執行代碼
在中斷處理程式中能避免并發通路的安全代碼稱作中斷安全代碼;在對稱多處理器的機器中能避免并發通路的安全代碼稱為SMP安全代碼(SMP-safe).在核心搶占的時候能避免并發通路的安全代碼稱為搶占安全代碼.
2:需要給那些資料加鎖
如果有其他線程可以通路這些資料,那麼就給這些資料加上鎖;如果任何其他什麼東西都能看到他,那麼就要鎖住他.記住:要給資料加鎖而不是給代碼加鎖.
(三)死鎖
死鎖的産生需要一定條件:要有一個或多個執行線程和一個或多個資源,每個線程都在等待其中的一個資源,但所有的資源都已經被占用了.所有的線程都在互相等待,但他們永遠不會釋放已經占有的資源.于是任何線程都無法繼續,這便意味着死鎖的發生.
最簡單的死鎖的例子是自死鎖:如果一個執行線程試圖去獲得一個自己已經持有的鎖,他将不得不等待鎖被釋放,但他正在忙着等待這個鎖,是以自己永遠不會有機會釋放所,最終結果是死鎖.
一些簡單的規則對避免死鎖有非常大的幫助:
1:按順序加鎖,盡量按相反的順序釋放鎖.使用嵌套的鎖時,必須保證以相同的順序擷取鎖,這樣可以阻止緻命擁抱類型的死鎖.
2:防止發生饑餓
3:不要重複請求同一個鎖
4:設計力求簡單--越複雜的加鎖方案有可能造成死鎖
(四):争用和擴充性
鎖的争用,或簡稱争用,是指當鎖正在被占用的時候,有其他線程試圖獲得該鎖.說一個鎖處于高度争用狀态,就是指有多個其他線程在等待獲得該鎖.
擴充性是對系統可擴充程度的一個度量.