.Net线程同步技术解读
C#開發(fā)者(面試者)都會遇到lock(Monitor),Mutex,Semaphore,SemaphoreSlim這四個與鎖相關(guān)的C#類型,本文期望以最簡潔明了的方式闡述四種對象的區(qū)別。
什么是線程安全
教條式理解如果代碼在多線程環(huán)境中運(yùn)行的結(jié)果與單線程運(yùn)行結(jié)果一樣,其他變量值也和預(yù)期是一樣的,那么線程就是安全的;
結(jié)合場景理解兩個線程都為集合增加元素,我們錯誤的理解即使是多線程也總有先后順序吧,集合的兩個位置先后塞進(jìn)去就完了;實際上集合增加元素這個行為看起來簡單,實際并不一定是原子操作。
在添加一個元素的時候,它可能會有兩步來完成:
在 Items[Size] 的位置存放此元素;
增大 Size 的值。
在單線程運(yùn)行的情況下,如果 Size = 0,添加一個元素后,此元素在位置0,之后設(shè)置Size=1;
如果是在多線程場景下,有兩個線程,線程A先將元素存放在位置0,但是此時CPU調(diào)度線程A暫停,線程B得到運(yùn)行機(jī)會;線程B也向此ArrayList添加元素,因為此時Size仍然等于0 (注意哦,我們假設(shè)添加元素是經(jīng)過兩個步驟,而線程A僅僅完成了步驟1),所以線程B也將元素存放在位置0。然后線程A和線程B都繼續(xù)運(yùn)行,都增加 Size 的值。那好,我們來看看ArrayList的情況,元素實際上只有一個,存放在位置 0,而Size卻等于2,形成了臟數(shù)據(jù),這種就定義為對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 靜態(tài)屬性的操作是線程安全的:Any public static members of this type (HttpContext) are thread safe, any instance members are not guaranteed to be thread safe.
各語言推出了適用于不同范圍的線程同步技術(shù)來預(yù)防以上臟數(shù)據(jù)(實現(xiàn)線程安全)
線程同步技術(shù)
話不多說,給出大圖:
四象限對象的區(qū)別:
支持線程進(jìn)入的個數(shù)
是否跨進(jìn)程支持?
上半?yún)^(qū) lock(Monitor), Mutex(中文稱為互斥鎖)都只支持單線程進(jìn)入被保護(hù)代碼,其他線程則必須等待進(jìn)入的線程完成 {Critical Section}
下半?yún)^(qū)SemaphoreSlim、Semaphore(中文稱為信號量)支持并發(fā)多線程進(jìn)入被保護(hù)代碼,對象在初始化時會指定 最大信號燈數(shù)量,當(dāng)線程請求訪問資源,信號量遞減,而當(dāng)他們釋放時,信號量計數(shù)又遞增。
左半?yún)^(qū)lock (Monitor)、SemaphoreSlim 是CRL對象,?進(jìn)程內(nèi)線程同步;?右半?yún)^(qū)Mutex,Semaphore都繼承自WaitHandle對象,支持命名,是內(nèi)核對象,在系統(tǒng)級別能支持進(jìn)程間線程同步。
進(jìn)程間線程同步不多見(分布式鎖的場景越來越多,這里按下不表),啰嗦一下常見的進(jìn)程內(nèi)線程同步技術(shù):
?①?lock(Monitor)
開發(fā)者最常用的lock關(guān)鍵字,使用方式相當(dāng)簡單,對于單進(jìn)程內(nèi)線程同步相當(dāng)有效,實際上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);
}
一般使用私有靜態(tài)對象作為lock(Monitor)線程同步的同步對象,那配合lock完成代碼鎖定的那個對象到底起什么作用呢?
? ?這里面有個SyncBlockIndex的概念,新建的托管堆在內(nèi)存表現(xiàn)如下:
? 每個堆對象:函數(shù)表指針(這也是一個重要知識點,用于在多態(tài)中判斷對象到底是哪個類型)、同步塊索引、對象字段;其中同步塊索引是lock解決線程同步的關(guān)鍵,SyncBlockIndex是一個地址指針(傳送門);
新創(chuàng)建的對象objLock,其SyncBlockindex =-1,不指向任何有效同步塊;
調(diào)用靜態(tài)類Monitor.Enter(objLock), CRL會尋找一個空閑SyncBlock并將objLock對象的SyncBlockIndex指向該塊, 例如上圖中ObjectA,ObjectC的SyncBlockIndex指向了2個SyncBlock;
Exit(objLock)會將objLock對象的SyncBlockIndex重新賦為 -1, 釋放出來的SyncBlock可以在將來被其他對象SyncBlockIndex引用。
② lock(Monitor)?vs SemaphoreSlim
????兩者都是進(jìn)程內(nèi)線程同步技術(shù),SemaphoreSlim信號量支持多線程進(jìn)入;另外SemaphoreSlim 有異步等待方法,支持在異步代碼中線程同步,解決在async code中無法使用lock語法糖的問題
//?實例化單信號量static?SemaphoreSlim?semaphoreSlim?=?new?SemaphoreSlim(1,1);
//?異步等待進(jìn)入信號量,如果沒有線程被授予對信號量的訪問權(quán)限,則進(jìn)入執(zhí)行保護(hù)代碼;否則此線程將在此處等待,直到信號量被釋放為止
await?semaphoreSlim.WaitAsync();
try
{
????await?Task.Delay(1000);
}
finally
{
????//?任務(wù)準(zhǔn)備就緒后,釋放信號燈。【準(zhǔn)備就緒時始終釋放信號量】至關(guān)重要,否則我們將獲得永遠(yuǎn)被鎖定的信號量
????semaphoreSlim.Release();
}
總結(jié)
從宏觀上掌握Monitor,Mutex,SemaphoreSlim,Semaphore的區(qū)別有利于形成【線程同步知識體系】;文章著重記錄進(jìn)程內(nèi)線程同步技術(shù)。
總結(jié)
以上是生活随笔為你收集整理的.Net线程同步技术解读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Firefox UI已迁移至Web Co
- 下一篇: 【.NET Core 跨平台 GUI 开