C 執行緒鎖(下)

2021-09-08 06:55:33 字數 3470 閱讀 1649

前兩篇簡單介紹了執行緒同步lock,monitor,同步事件eventwaithandler,互斥體mutex的基本用法,在此基礎上,我們對它們用法進行比較,並給出什麼時候需要鎖什麼時候不需要的幾點建議。最後,介紹幾個fcl中線程安全的類,集合類的鎖定方式等,做為對執行緒同步系列的完善和補充。

1.幾種同步方法的區別

lock和monitor是.net用乙個特殊結構實現的,monitor物件是完全託管的、完全可移植的,並且在作業系統資源要求方面可能更為有效,同步速度較快,但不能跨程序同步。lock(monitor.enter和monitor.exit方法的封裝),主要作用是鎖定臨界區,使臨界區**只能被獲得鎖的執行緒執行。monitor.wait和monitor.pulse用於執行緒同步,類似訊號操作,個人感覺使用比較複雜,容易造成死鎖。

互斥體mutex和事件物件eventwaithandler屬於核心物件,利用核心物件進行執行緒同步,執行緒必須要在使用者模式和核心模式間切換,所以一般效率很低,但利用互斥物件和事件物件這樣的核心物件,可以在多個程序中的各個執行緒間進行同步。

互斥體mutex類似於乙個接力棒,拿到接力棒的執行緒才可以開始跑,當然接力棒一次只屬於乙個執行緒(thread affinity),如果這個執行緒不釋放接力棒(mutex.releasemutex),那麼沒辦法,其他所有需要接力棒執行的執行緒都知道能等著看熱鬧。

eventwaithandle 類允許執行緒通過發訊號互相通訊。通常,乙個或多個執行緒在 eventwaithandle 上阻止,直到乙個未阻止的執行緒呼叫 set 方法,以釋放乙個或多個被阻止的執行緒。

2.什麼時候需要鎖定

首先要理解鎖定是解決競爭條件的,也就是多個執行緒同時訪問某個資源,造成意想不到的結果。比如,最簡單的情況是,乙個計數器,兩個執行緒 同時加一,後果就是損失了乙個計數,但相當頻繁的鎖定又可能帶來效能上的消耗,還有最可怕的情況死鎖。那麼什麼情況下我們需要使用鎖,什麼情況下不需要 呢?

1)只有共享資源才需要鎖定

只有可以被多執行緒訪問的共享資源才需要考慮鎖定,比如靜態變數,再比如某些快取中的值,而屬於執行緒內部的變數不需要鎖定。 

2)多使用lock,少用mutex

如果你一定要使用鎖定,請盡量不要使用核心模組的鎖定機制,比如.net的mutex,semaphore,autoresetevent和 manuresetevent,使用這樣的機制涉及到了系統在使用者模式和核心模式間的切換,效能差很多,但是他們的優點是可以跨程序同步執行緒,所以應該清 楚的了解到他們的不同和適用範圍。

4)把鎖定交給資料庫

數 據庫除了儲存資料之外,還有乙個重要的用途就是同步,資料庫本身用了一套複雜的機制來保證資料的可靠和一致性,這就為我們節省了很多的精力。保證了資料來源 頭上的同步,我們多數的精力就可以集中在快取等其他一些資源的同步訪問上了。通常,只有涉及到多個執行緒修改資料庫中同一條記錄時,我們才考慮加鎖。 

5)業務邏輯對事務和執行緒安全的要求

這 條是最根本的東西,開發完全執行緒安全的程式是件很費時費力的事情,在電子商務等涉及金融系統的案例中,許多邏輯都必須嚴格的執行緒安全,所以我們不得不犧牲 一些效能,和很多的開發時間來做這方面的工作。而一般的應用中,許多情況下雖然程式有競爭的危險,我們還是可以不使用鎖定,比如有的時候計數器少一多一, 對結果無傷大雅的情況下,我們就可以不用去管它。

3.interlocked類

interlocked 類提供了同步對多個執行緒共享的變數的訪問的方法。如果該變數位於共享記憶體中,則不同程序的執行緒就可以使用該機制。互鎖操作是原子的,即整個操作是不能由相 同變數上的另乙個互鎖操作所中斷的單元。這在搶先多執行緒作業系統中是很重要的,在這樣的作業系統中,執行緒可以在從某個記憶體位址載入值之後但是在有機會更改 和儲存該值之前被掛起。

我們來看乙個interlock.increment()的例子,該方法以原子的形式遞增指定變數並儲存結果,示例如下:

class

interlockedtest

}public

static

void

main(

string

args)}

輸出結果200000000,如果interlockedtest.add()方法中用注釋掉的語句代替interlocked.increment()方法,結果將不可預知,每次執行結果不同。interlockedtest.add()方法保證了加1操作的原子性,功能上相當於自動給加操作使用了lock鎖。同時我們也注意到interlockedtest.add()用時比直接用+號加1要耗時的多,所以說加鎖資源損耗還是很明顯的。

另外interlockedtest類還有幾個常用方法,具體用法可以參考msdn上的介紹。

4.集合類的同步

.net在一些集合類,比如queue、arraylist、hashtable和stack,已經提供了乙個供lock使用的物件syncroot。用reflector檢視了syncroot屬性(stack.synchroot略有不同)的原始碼如下:

public

virtual

object

syncroot

return

this

._syncroot;}}

這裡要特別注意的是msdn提到:從頭到尾對乙個集合進行列舉本質上並不是乙個執行緒安全的過程。即使乙個集合已進行同步,其他執行緒仍可以修改該集合,這將導致列舉數引發異常。若要在列舉過程中保證執行緒安全,可以在整個列舉過程中鎖定集合,或者捕捉由於其他執行緒進行的更改而引發的異常。應該使用下面的**:

queue q 

=new

queue();

lock

(q.syncroot)}

//在多執行緒環境中只要我們用下面的方式例項化hashtable就可以了

hashtable ht 

=hashtable.synchronized(

newhashtable());

//以下**是.net framework class library實現,增加對synchronized的認識

[hostprotection(securityaction.linkdemand, synchronization

=true

)]public

static

hashtable synchronized(hashtable table)

return

newsynchashtable(table);}//

synchashtable的幾個常用方法,我們可以看到內部實現都加了lock關鍵字保證執行緒安全

public

override

void

add(

object

key, 

object

value)

}public

override

void

clear()

}public

override

void

remove(

object

key)}

C 執行緒鎖(下)

前兩篇簡單介紹了執行緒同步lock,monitor,同步事件eventwaithandler,互斥體mutex的基本用法,在此基礎上,我們對它們用法進行比較,並給出什麼時候需要鎖什麼時候不需要的幾點建議。最後,介紹幾個fcl中線程安全的類,集合類的鎖定方式等,做為對執行緒同步系列的完善和補充。1.幾...

C 執行緒的鎖

c 不加鎖的多執行緒例項 include include using namespace std int num 100 void fun1 else int main cout main thread endl for int i 0 i 20 i return 0 顯示的結果如下 mu.unlo...

Linux 下的執行緒讀寫鎖

linux 下的執行緒讀寫鎖 有一種寫優先讀寫鎖,有如下特點 1 多個讀者可以同時進行讀 2 寫者必須互斥 只允許乙個寫者寫,也不能讀者寫者同時進行 3 寫者優先於讀者 一旦有寫者,則後續讀者必須等待,喚醒時優先考慮寫者 在solaris 中直接提供了讀寫鎖,但是在linux 中只提供了執行緒的讀寫...