一文看懂 Mutex vs Semaphore vs Monitor vs SemaphoreSlim
- 2019 年 10 月 30 日
- 筆記
C#開發者(面試者)都會遇到Mutex,Semaphore,Monitor,SemaphoreSlim這四個與鎖相關的C#類型,本文期望以最簡潔明了的方式闡述四種對象的區別。
什麼叫執行緒安全?
教條式理解
如果程式碼在多執行緒環境中運行的結果與 單執行緒運行結果一樣,其他變數值也和預期是一樣的,那麼執行緒就是安全的;
執行緒不安全就是不提供數據訪問保護,可能出現多個執行緒先後修改數據造成的結果是臟數據。
實際場景理解
兩個執行緒都為集合增加元素,我們錯誤的理解即使是多執行緒也總有先後順序吧,集合的兩個位置先後塞進去就完了;實際上集合增加元素這個行為看起來簡單,實際並不一定是原子操作。
在添加一個元素的時候,它可能會有兩步來完成:
- 在 Items[Size] 的位置存放此元素;
- 增大 Size 的值。
-
在單執行緒運行的情況下,如果 Size = 0,添加一個元素後,此元素在位置0,之後設置Size=1;
-
如果是在多執行緒場景下,有兩個執行緒,執行緒A先將元素存放在位置0,但是此時CPU調度執行緒A暫停,執行緒B得到運行機會;執行緒B也向此ArrayList添加元素,因為此時Size仍然等於0 (注意哦,我們假設添加元素是經過兩個步驟,而執行緒A僅僅完成了步驟1),所以執行緒B也將元素存放在位置0。然後執行緒A和執行緒B都繼續運行,都增加 Size 的值。 那好,我們來看看ArrayList的情況,元素實際上只有一個,存放在位置 0,而Size卻等於2,形成了臟數據,這種就定義為對ArrayList的新增元素操作是執行緒不安全的。
執行緒安全這個問題不單單存在於集合類,我們始終要記得:
Never ever modify a shared resource by multipie threads unless resource is thread-safe.
-
我們對SqlServer,Mongodb,對HttpContext的訪問都會涉及thread-safe, 利用C# mongodb driver操作Mongo打包時常用操作是執行緒安全的,Only a few of the C# Driver classes are thread safe. Among them: MongoServer, MongoDatabase, MongoCollection and MongoGridFS.
-
對於HttpContext 靜態屬性的操作是執行緒安全的: Any public static members of this type (HttpContext) are thread safe, any instance members are not guaranteed to be thread safe. 我們常用的是HttpContext.Current
各語言推出了適用於不同範圍的執行緒同步技術來預防以上臟數據(實現執行緒安全)。
C#執行緒同步技術
話不多說, 給出大圖:
四象限對象的區別:
該執行緒同步技術
- 支援執行緒進入的個數
– 是否跨進程支援
其中
① lock vs Monitor
最常用的lock關鍵字,能在多執行緒環境下確保只有一個執行緒在執行 {被保護的程式碼},其他執行緒則必須等待進入的執行緒完成工作程式碼。
上圖將lock和Monitor放在一起,是因為lock是Monitor的語法糖,實際的編譯程式碼如下:
bool lockTaken = false; try { Monitor.Enter(obj, ref lockTaken); //... } finally { if (lockTaken) Monitor.Exit(obj); }
② lock(Monitor)vs Mutex(中文稱為互斥鎖,互斥元)
lock/Monitor 維護進程內執行緒的安全性,Mutex 維護跨進程的執行緒安全性。
這2個對象都只支援單執行緒進入指定程式碼。
③ SemaphoreSlim vs Semaphore
中文都稱為訊號量,根據對象初始化的配置,能夠允許單個或多個執行緒進入保護程式碼。
訊號量使多個並發執行緒可以訪問共享資源(最大為您指定的最大數量),當執行緒請求訪問資源時,訊號量計數遞減,而當它們釋放資源時,訊號量計數又遞增。
SemaphoreSlim 是一個輕量級的,由CRL支援的進程內訊號量。
右側Mutex 和Semaphore 都是內核對象,可以看到他們都繼承自WaitHandle對象,
左側Monitor,SemaphoreSlim是.NET CLR對象,
④ Monitor vs SemaphoreSlim
兩者都是進程內執行緒同步技術,SemaphoreSlim訊號量支援多執行緒進入;
另外SemaphoreSlim 有非同步等待方法,支援在非同步程式碼中執行緒同步, 能解決我們在async code中無法使用lock語法糖的問題;
// 實例化單訊號量 static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1,1); //Asynchronously wait to enter the Semaphore. If no-one has been granted access to the Semaphore, code execution will proceed, otherwise this thread waits here until the semaphore is released await semaphoreSlim.WaitAsync(); try { await Task.Delay(1000); } finally { //When the task is ready, release the semaphore. It is vital to ALWAYS release the semaphore when we are ready, or else we will end up with a Semaphore that is forever locked. //This is why it is important to do the Release within a try...finally clause; program execution may crash or take a different path, this way you are guaranteed execution semaphoreSlim.Release(); }
總結:
象限圖中快速知曉 這4種執行緒同步技術的區別:
– 是否支援跨進程執行緒同步
– 是否支援多執行緒進入被保護程式碼。