注:摘自《程式設計師的自我修養》相關章節。
關鍵字:執行緒同步、原子操作、鎖、二元訊號量、訊號量、互斥量、臨界區、讀寫鎖、條件變數
原子操作
共享資料(全域性變數或堆變數)的自增(++)操作在多執行緒環境下會出現錯誤是因為這個操作(一條c語句)被編譯為彙編**後不止一條指令,因此在執行的時候可能執行了一半就被排程系統打斷,去執行別的**。
我們把單指令的操作稱為原子的(atomic),因為無論如何,單條指令的執行是不會被打斷的。為了避免出錯,很多體系結構都提供了一些常用操作的原子指令,例如i386就有一條inc指令可以直接增加乙個記憶體單元值。
在windows裡,有一套api專門進行一些原子操作(見下表),這些api稱為interlockedapi。
windows api 作用
interlockedexchange 原子地交換兩個值
interlockeddecrement 原子地減少乙個值
interlockedincrement 原子地增加乙個值
interlockedxor 原子地進行異或操作
使用這些函式時,windows將保證是原子操作的,因此可以不用擔心出現問題。遺憾的是,儘管原子操作指令非常方便,但是它們僅僅適用於比較簡單的特定場合。在複雜的場合下,比如我們要保證乙個複雜的資料結構更改的原子性,原子操作就力不從心了。這裡我們就需要更加通用的手段:鎖。
同步與鎖
為了避免多個執行緒同時讀寫同乙個資料(全域性變數或堆變數)而產生不可預料的後果,我們需要將各個執行緒對同乙個資料的訪問同步(synchronization)。所謂同步,即是指在乙個執行緒訪問資料未結束的時候,其他執行緒不得對同乙個資料進行訪問。如此,對資料的訪問被原子化了。
同步的最常見方法是使用鎖(lock)。即每個執行緒在訪問資料或資源之前首先試圖獲取(acquire)鎖,並在訪問結束後釋放(release)鎖。在鎖已經被占用的時候試圖獲取鎖時,執行緒會等待,直到鎖重新可用。
二元訊號量(binary semaphore)是最簡單的一種鎖,它只用兩種狀態:占用與非占用。它適合只能被唯一乙個執行緒訪問的資源。當二元訊號量處於非占用狀態時,第乙個試圖獲取該二元訊號量的執行緒會獲得該鎖,並將二元訊號量置為占用狀態,此後其他的所有試圖獲取該二元訊號量的執行緒將會等待,知道該鎖被釋放。
對於允許多個執行緒併發訪問的資源,多元訊號量簡稱為訊號量(semaphore),它是乙個很好的選擇。乙個初始值為n的訊號量允許n個執行緒併發訪問。執行緒訪問資源的時候首先獲取訊號量,進行如下操作:
■將訊號量的值減1
■如果訊號量的值小於0,則進入等待狀態,否則繼續執行。
訪問資源之後,執行緒釋放訊號量,進行如下操作:
■將訊號量的值加1.
■如果訊號量的值小於1,喚醒乙個等待中的執行緒。
互斥量(mutex)和二元訊號量很類似,即資源僅同時允許乙個執行緒訪問,但和訊號量不同的是,訊號量在整個系統可以被任意執行緒獲取並釋放,也就是說,同乙個訊號量可以被系統中的乙個執行緒獲取之後由另乙個執行緒釋放。而互斥量則要求哪個執行緒獲取了互斥量,哪個執行緒就要負責釋放這個鎖,其他執行緒越俎代庖去釋放互斥量是無效的。
臨界區(critical section)是比互斥量更加嚴格的同步手段。在術語中,把臨界區的鎖的獲取稱為進入臨界區,而把鎖的釋放稱為離開臨界區。臨界區和互斥量與訊號量的區別在於,互斥量和訊號量在系統中任何程序裡都是可見的,也就是說,乙個程序建立了乙個互斥量或訊號量,另乙個程序試圖去獲取該鎖時合法的。然而,臨界區的作用範圍僅限於本程序中,其他的程序無法獲取該鎖(類似於靜態全域性變數對全域性變數)。除此之外,臨界區具有和互斥量相同的性質。
讀寫鎖(read-write lock)致力於一種更加特定的場合的同步。對於一段資料,多個執行緒同時讀取總是沒問題的,但假設操作都不是原子型,只要有任何乙個執行緒試圖對這個資料進行修改,就必須使用同步手段來避免出錯。如果我們使用上述訊號量、互斥量或臨界區中的任何一種來進行同步,儘管可以保證程式爭取,但對於讀取頻繁,而僅僅偶爾寫入的情況,會顯得非常低效。讀寫鎖可以避免這個問題。對於同乙個鎖,讀寫鎖由兩種獲取方式,共享的(shared)或獨佔的(exclusive)。當鎖處於自由的狀態時,試圖以任何一種方式獲取鎖都能成功,並將鎖置於對應的狀態。如果鎖處於共享狀態,其他執行緒以共享的方式獲取鎖仍然會成功,此時這個鎖分配給了多個執行緒。然而,如果其他執行緒試圖以獨佔的方式獲取已經處於共享狀態的鎖,那麼它必須等待鎖被所有的執行緒釋放。相應地,處於獨佔狀態的鎖將阻止任何其他執行緒獲取該鎖,不論它們試圖以哪種方式獲取。讀寫鎖的行為可以總結為如下表:
讀寫鎖狀態 以共享方式獲取 以獨佔方式獲取
自由 成功 成功
共享 成功 等待
獨佔 等待 等待
條件變數(condition variable)作為一種同步手段,作用類似於乙個柵欄。對於條件變數,執行緒可以有兩種操作,首先執行緒可以等待條件變數,乙個條件變數可以被多個執行緒等待。其次,執行緒可以喚醒條件變數,此時某個或所有等待此條件變數的執行緒都會被喚醒並繼續支援。也就是說,使用條件變數可以讓許多執行緒一起等待某個事件的發生,當事件發生時(條件變數被喚醒),所有的執行緒可以一起恢復執行。
訊號量 互斥鎖
訊號量與普通整型變數的區別 訊號量 semaphore 是非負整型變數,除了初始化之外,它只能通過兩個標準原子操作 wait semap signal semap 來進行訪問 操作也被成為pv原語 p 於dutch proberen 測試 v 於 dutch verhogen 增加 而普通整型變數則...
自旋鎖,互斥鎖,訊號量
自旋鎖,互斥鎖,訊號量 樂觀鎖和悲觀鎖只是一種理論,是從思想上劃分的。自旋鎖和互斥鎖是應用層確確實實的鎖,用於同步訪問控制 如果一定要劃分,從只有乙個執行緒可以擁有鎖來說,我覺得自旋鎖和互斥鎖應該都屬於悲觀鎖,因為一般的應用不需要支援事物回滾的操作。但是沈詢的直播中說,互斥鎖屬於悲觀鎖 sleep ...
訊號量,互斥鎖,自旋鎖
個人理解 訊號量 程序間的通訊機制 單一個數的訊號 與訊息郵箱,訊息佇列,機理類同,量不同,用訊號量肯定掉cpu 自旋鎖 保護區域不掉cpu,持續查詢,等待 不可用時域長狀態 切記 時域範圍 在程序間的通訊機制函式狀態 鎖 0 互斥鎖與自旋鎖 互斥鎖 執行緒會從sleep 加鎖 running 解鎖...