1. 程式人生 > >一文看懂.Net執行緒同步技術 lock(Monitor) vs Mutex vs Semaphore vs SemaphoreSlim

一文看懂.Net執行緒同步技術 lock(Monitor) vs Mutex vs Semaphore vs SemaphoreSlim

C#開發者(面試者)都會遇到lock(Monitor),Mutex,Semaphore,SemaphoreSlim這四個與鎖相關的C#型別,本文期望以最簡潔明瞭的方式闡述四種物件的區別。

什麼叫執行緒安全?

教條式理解

如果程式碼在多執行緒環境中執行的結果與 單執行緒執行結果一樣,其他變數值也和預期是一樣的,那麼執行緒就是安全的;

執行緒不安全就是不提供資料訪問保護,可能出現多個執行緒先後修改資料造成的結果是髒資料。

實際場景理解 

兩個執行緒都為集合增加元素,我們錯誤的理解即使是多執行緒也總有先後順序吧,集合的兩個位置先後塞進去就完了;實際上集合增加元素這個行為看起來簡單,實際並不一定是原子操作。

在新增一個元素的時候,它可能會有兩步來完成:

  1. 在 Items[Size] 的位置存放此元素;
  2. 增大 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);

// 非同步等待進入訊號量,如果沒有執行緒被授予對訊號量的訪問許可權,則進入執行保護程式碼;否則此執行緒將在此處等待,直到訊號量被釋放為止
await semaphoreSlim.WaitAsync();
try
{
    await Task.Delay(1000);
}
finally
{
    // 任務準備就緒後,釋放訊號燈。【準備就緒時始終釋放訊號量】至關重要,否則我們將獲得永遠被鎖定的訊號量
// 這就是為什麼在try ... finally子句中進行釋出很重要的原因;程式執行可能會崩潰或採用其他路徑,這樣可以保證執行 semaphoreSlim.Release(); }

從目前看跨程序執行緒同步很少見,倒是分散式鎖越來越多常見了。

總結:

文章沒有講述每個物件使用方式,自行看MSDN doc。從象限圖中快速知曉 這4種執行緒同步技術的區別:

-   是否支援跨程序執行緒同步

-   是否支援多執行緒進入被保護程式碼。

從巨集觀上了解四個物件的區別對於【執行緒同步】知識體系的形成是有幫助的。