C#開發者(面試者)都會遇到lock(Monitor),Mutex,Semaphore,SemaphoreSlim這四個與鎖相關的C#類型,本文指望以最簡潔明瞭的方式闡述四種對象的區別。面試
若是代碼在多線程環境中運行的結果與 單線程運行結果同樣,其餘變量值也和預期是同樣的,那麼線程就是安全的;mongodb
線程不安全就是不提供數據訪問保護,可能出現多個線程前後修改數據形成的結果是髒數據。安全
兩個線程都爲集合增長元素,咱們錯誤的理解即便是多線程也總有前後順序吧,集合的兩個位置前後塞進去就完了;實際上集合增長元素這個行爲看起來簡單,實際並不必定是原子操做。多線程
在添加一個元素的時候,它可能會有兩步來完成:併發
在單線程運行的狀況下,若是 Size = 0,添加一個元素後,此元素在位置0,以後設置Size=1;less
若是是在多線程場景下,有兩個線程,線程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.async
咱們對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函數
各語言推出了適用於不一樣範圍的線程同步技術來預防以上髒數據(實現線程安全)。
話很少說, 給出大圖:
四象限對象的區別:
該線程同步技術
上半區 lock(Monitor), Mutex(中文稱爲互斥鎖)都只支持單線程進入被保護代碼,其餘線程則必須等待進入的線程完成 {Critical Section}
下半區SemaphoreSlim、Semaphore(中文稱爲信號量)支持併發多線程進入被保護代碼,對象在初始化時會指定 最大信號燈數量,當線程請求訪問資源,信號量遞減,而當他們釋放時,信號量計數又遞增。
左半區lock (Monitor)、SemaphoreSlim 是CRL對象, 進程內線程同步; 右半區Mutex,Semaphore都繼承自WaitHandle對象,支持命名,是內核對象,在系統級別能支持進程間線程同步。
進程間線程同步很少見(分佈式鎖的場景愈來愈多,這裏按下不表),囉嗦一下常見的進程內線程同步技術:
① lock(Monitor)
開發者最經常使用的lock關鍵字,使用方式至關簡單,對於單進程內線程同步至關有效,
實際上lock是Monitor的語法糖,實際的編譯代碼以下:
object __lockObj = x; bool __lockWasTaken = false; try { System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken); // Your code... } finally { if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj); }
通常使用私有靜態對象做爲lock(Monitor)線程同步的同步對象,那配合lock完成代碼鎖定的那個對象到底起什麼做用呢?
這裏面有個SyncBlockIndex的概念,新建的託管堆在內存表現以下:
每一個堆對象: 函數表指針(這也是一個重要知識點,用於在多態中判斷對象究竟是哪一個類型)、同步塊索引、對象字段;
其中同步塊索引是lock解決線程同步的關鍵,SyncBlockIndex是一個地址指針(傳送門);
新建立的對象objLock, 其SyncBlockindex =-1, 不指向任何有效同步塊;
調用靜態類Monitor.Enter(objLock), CRL會尋找一個空閒SyncBlock並將objLock對象的SyncBlockIndex指向該塊, 例如上圖中ObjectA,ObjectC的SyncBlockIndex指向了2個SyncBlock;
Exit(objLock)會將objLock對象的SyncBlockIndex從新賦爲 -1, 釋放出來的SyncBlock能夠在未來被其餘對象SyncBlockIndex引用。
② lock(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(); }
從宏觀上掌握 Monitor,Mutex, SemaphoreSlim,Semaphore的區別有利於造成【線程同步知識體系】;
文章着重記錄 進程內線程同步技術。