1. 程式人生 > >互斥量和訊號量的理解

互斥量和訊號量的理解

互斥量(Mutex)

 

互斥量表現互斥現象的資料結構,也被當作二元訊號燈。一個互斥基本上是一個多工敏感的二元訊號,它能用作同步多工的行為,它常用作保護從中斷來的臨界段程式碼並且在共享同步使用的資源。

clip_image001

 

Mutex本質上說就是一把鎖,提供對資源的獨佔訪問,所以Mutex主要的作用是用於互斥。Mutex物件的值,只有0和1兩個值。這兩個值也分別代表了Mutex的兩種狀態。值為0, 表示鎖定狀態,當前物件被鎖定,使用者程序/執行緒如果試圖Lock臨界資源,則進入排隊等待;值為1,表示空閒狀態,當前物件為空閒,使用者程序/執行緒可以Lock臨界資源,之後Mutex值減1變為0。

Mutex可以被抽象為四個操作:

- 建立 Create

- 加鎖 Lock

- 解鎖 Unlock

- 銷燬 Destroy

Mutex被建立時可以有初始值,表示Mutex被建立後,是鎖定狀態還是空閒狀態。在同一個執行緒中,為了防止死鎖,系統不允許連續兩次對Mutex加鎖(系統一般會在第二次呼叫立刻返回)。也就是說,加鎖和解鎖這兩個對應的操作,需要在同一個執行緒中完成。

不同作業系統中提供的Mutex函式:

動作/系統

Win32

Linyx

Solaris

建立

CreateMutex

pthread_mutex_init

mutex_init

加鎖

WaitForSingleObject

pthread_mutex_lock

mutex_lock

解鎖

ReleaseMutex

pthread_mutex_unlock

mutex_unlock

銷燬

CloseHandle

pthread_mutex_destroy

mutex_destroy

 

 

死鎖主要發生在有多個依賴鎖存在時, 會在一個執行緒試圖以與另一個執行緒相反順序鎖住互斥量時發生. 如何避免死鎖是使用互斥量應該格外注意的東西。

  總體來講, 有幾個不成文的基本原則:

  對共享資源操作前一定要獲得鎖。

  完成操作以後一定要釋放鎖。

  儘量短時間地佔用鎖。

  如果有多鎖, 如獲得順序是ABC連環扣, 釋放順序也應該是ABC。

  執行緒錯誤返回時應該釋放它所獲得的鎖。

 

也許還有讀者好奇,“掛起等待”和“喚醒等待執行緒”的操作如何實現?每個Mutex有一個等待佇列,一個執行緒要在Mutex上掛起等待,首先在把自己加入等待佇列中,然後置執行緒狀態為睡眠,然後呼叫排程器函式切換到別的執行緒。一個執行緒要喚醒等待佇列中的其它執行緒,只需從等待佇列中取出一項,把它的狀態從睡眠改為就緒,加入就緒佇列,那麼下次排程器函式執行時就有可能切換到被喚醒的執行緒。

 

一般情況下,如果同一個執行緒先後兩次呼叫lock,在第二次呼叫時,由於鎖已經被佔用,該執行緒會掛起等待別的執行緒釋放鎖,然而鎖正是被自己佔用著的,該執行緒又被掛起而沒有機會釋放鎖,因此就永遠處於掛起等待狀態了,這叫做死鎖(Deadlock)。另一種典型的死鎖情形是這樣:執行緒A獲得了鎖1,執行緒B獲得了鎖2,這時執行緒A呼叫lock試圖獲得鎖2,結果是需要掛起等待執行緒B釋放鎖2,而這時執行緒B也呼叫lock試圖獲得鎖1,結果是需要掛起等待執行緒A釋放鎖1,於是執行緒A和B都永遠處於掛起狀態了。不難想象,如果涉及到更多的執行緒和更多的鎖,有沒有可能死鎖的問題將會變得複雜和難以判斷。

 

訊號量

訊號量(Semaphore),有時被稱為訊號燈,是在多執行緒環境下使用的一種設施, 它負責協調各個執行緒, 以保證它們能夠正確、合理的使用公共資源。

訊號量可以分為幾類:

² 二進位制訊號量(binary semaphore):只允許訊號量取0或1值,其同時只能被一個執行緒獲取。

² 整型訊號量(integer semaphore):訊號量取值是整數,它可以被多個執行緒同時獲得,直到訊號量的值變為0。

² 記錄型訊號量(record semaphore):每個訊號量s除一個整數值value(計數)外,還有一個等待佇列List,其中是阻塞在該訊號量的各個執行緒的標識。當訊號量被釋放一個,值被加一後,系統自動從等待佇列中喚醒一個等待中的執行緒,讓其獲得訊號量,同時訊號量再減一。

訊號量通過一個計數器控制對共享資源的訪問,訊號量的值是一個非負整數,所有通過它的執行緒都會將該整數減一。如果計數器大於0,則訪問被允許,計數器減1;如果為0,則訪問被禁止,所有試圖通過它的執行緒都將處於等待狀態。

計數器計算的結果是允許訪問共享資源的通行證。因此,為了訪問共享資源,執行緒必須從訊號量得到通行證, 如果該訊號量的計數大於0,則此執行緒獲得一個通行證,這將導致訊號量的計數遞減,否則,此執行緒將阻塞直到獲得一個通行證為止。當此執行緒不再需要訪問共享資源時,它釋放該通行證,這導致訊號量的計數遞增,如果另一個執行緒等待通行證,則那個執行緒將在那時獲得通行證。

 

 

Semaphore可以被抽象為五個操作:

- 建立 Create

- 等待 Wait:

執行緒等待訊號量,如果值大於0,則獲得,值減一;如果只等於0,則一直執行緒進入睡眠狀態,知道訊號量值大於0或者超時。

-釋放 Post

執行釋放訊號量,則值加一;如果此時有正在等待的執行緒,則喚醒該執行緒。

-試圖等待 TryWait

如果呼叫TryWait,執行緒並不真正的去獲得訊號量,還是檢查訊號量是否能夠被獲得,如果訊號量值大於0,則TryWait返回成功;否則返回失敗。

-銷燬 Destroy

訊號量,是可以用來保護兩個或多個關鍵程式碼段,這些關鍵程式碼段不能併發呼叫。在進入一個關鍵程式碼段之前,執行緒必須獲取一個訊號量。如果關鍵程式碼段中沒有任何執行緒,那麼執行緒會立即進入該框圖中的那個部分。一旦該關鍵程式碼段完成了,那麼該執行緒必須釋放訊號量。其它想進入該關鍵程式碼段的執行緒必須等待直到第一個執行緒釋放訊號量。為了完成這個過程,需要建立一個訊號量,然後將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個關鍵程式碼段的首末端。確認這些訊號量VI引用的是初始建立的訊號量。

動作/系統

Win32

POSIX

建立

CreateSemaphore

sem_init

等待

WaitForSingleObject

sem _wait

釋放

ReleaseMutex

sem _post

試圖等待

WaitForSingleObject

sem _trywait

銷燬

CloseHandle

sem_destroy

 

 

 (生產者 消費者)

 

 

 

互斥量和訊號量的區別

1. 互斥量用於執行緒的互斥,訊號線用於執行緒的同步。

這是互斥量和訊號量的根本區別,也就是互斥和同步之間的區別。

互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源

2. 互斥量值只能為0/1,訊號量值可以為非負整數。

也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多執行緒互斥問題。訊號量可以實現多個同類資源的多執行緒互斥和同步。當訊號量為單值訊號量是,也可以完成一個資源的互斥訪問。

3. 互斥量的加鎖和解鎖必須由同一執行緒分別對應使用,訊號量可以由一個執行緒釋放,另一個執行緒得到。