在多線(xiàn)程環(huán)境中,多個(gè)線(xiàn)程可能會(huì)同時(shí)訪(fǎng)問(wèn)同一個(gè)資源,為了避免訪(fǎng)問(wèn)發(fā)生沖突,可以根據(jù)訪(fǎng)問(wèn)的復(fù)雜程度采取不同的措施

          原子操作適用于簡(jiǎn)單的單個(gè)操作,無(wú)鎖算法適用于相對(duì)簡(jiǎn)單的一連串操作,而線(xiàn)程鎖適用于復(fù)雜的一連串操作

          原子操作

          修改狀態(tài)要么成功且狀態(tài)改變,要么失敗且狀態(tài)不變,并且外部只能觀察到修改前或者修改后的狀態(tài),修改中途的狀態(tài)不能被觀察到

          .NET 中,System.Threading.Interlocked
          類(lèi)提供了用于執(zhí)行原子操作的函數(shù),這些函數(shù)接收引用參數(shù)(ref),也就是變量的內(nèi)存地址,然后針對(duì)該內(nèi)存地址中的值執(zhí)行原子操作

          無(wú)鎖算法

          不使用線(xiàn)程鎖,通過(guò)修改操作的內(nèi)容使它們滿(mǎn)足原子操作的條件

          .NET 提供了一些線(xiàn)程安全的數(shù)據(jù)類(lèi)型,這些數(shù)據(jù)類(lèi)型大量應(yīng)用了無(wú)鎖算法來(lái)提升訪(fǎng)問(wèn)速度(在部分情況下仍需要線(xiàn)程鎖):

          System.Collections.Consurrent.CurrentBag

          System.Collections.Consurrent.CurrentDictionary<TKey, TValue>

          System.Collections.Consurrent.CurrentQueue

          System.Collections.Consurrent.CurrentStack

          線(xiàn)程鎖


          有獲取鎖(Acquire)和釋放鎖(Release)兩個(gè)操作,在獲取鎖之后和釋放鎖之前進(jìn)行的操作保證在同一個(gè)時(shí)間只有一個(gè)線(xiàn)程執(zhí)行,操作內(nèi)容無(wú)需改變,所以線(xiàn)程鎖具有很強(qiáng)的通用性

          線(xiàn)程鎖有不同的種類(lèi),下面將分別介紹自旋鎖,互斥鎖,混合鎖,讀寫(xiě)鎖

          自旋鎖

          自旋鎖(Spinlock)是最簡(jiǎn)單的線(xiàn)程鎖,基于原子操作實(shí)現(xiàn)

          它使用一個(gè)數(shù)值來(lái)表示鎖是否已經(jīng)被獲取,0表示未被獲取,1表示已經(jīng)獲取

          獲取鎖時(shí)會(huì)先使用原子操作設(shè)置數(shù)值為1,然后檢查修改前的值是否為0,如果為0則代表獲取成功,否則繼續(xù)重試直到成功為止

          釋放鎖時(shí)會(huì)設(shè)置數(shù)值為0,其他正在獲取鎖的線(xiàn)程會(huì)在下一次重試時(shí)成功獲取

          使用原子操作的原因是,它可以保證多個(gè)線(xiàn)程同時(shí)把數(shù)值0修改到1時(shí),只有一個(gè)線(xiàn)程可以觀察到修改前的值為0,其他線(xiàn)程觀察到修改前的值為1

          .NET 可以使用以下的類(lèi)實(shí)現(xiàn)自旋鎖:

          System.Threading.Thread.SpinWait

          System.Threading.SpinWait

          System.Threading.SpinLock

          使用自旋鎖有個(gè)需要注意的問(wèn)題,自旋鎖保護(hù)的代碼應(yīng)該在非常短的時(shí)間內(nèi)執(zhí)行完畢,如果代碼長(zhǎng)時(shí)間運(yùn)行則其他需要獲取鎖的線(xiàn)程會(huì)不斷重試并占用邏輯核心,影響其他線(xiàn)程運(yùn)行

          此外,如果 CPU 只有一個(gè)邏輯核心,自旋鎖在獲取失敗時(shí)應(yīng)該立刻調(diào)用 Thread.Yield
          函數(shù)提示操作系統(tǒng)切換到其他線(xiàn)程,因?yàn)橐粋€(gè)邏輯核心同一時(shí)間只能運(yùn)行一個(gè)線(xiàn)程,在切換線(xiàn)程之前其他線(xiàn)程沒(méi)有機(jī)會(huì)運(yùn)行,也就是切換線(xiàn)程之前自旋鎖沒(méi)有機(jī)會(huì)被釋放

          互斥鎖

          由于自旋鎖不適用于長(zhǎng)時(shí)間運(yùn)行,它的使用場(chǎng)景比較有限,更通用的線(xiàn)程鎖是操作系統(tǒng)提供的基于原子操作與線(xiàn)程調(diào)度實(shí)現(xiàn)的互斥鎖(Mutex)


          與自旋鎖一樣,操作系統(tǒng)提供的互斥鎖內(nèi)部有一個(gè)數(shù)值表示是否已經(jīng)被獲取,不同的是當(dāng)獲取鎖失敗時(shí),它不會(huì)反復(fù)重試,而是安排獲取鎖的線(xiàn)程進(jìn)入等待狀態(tài),并把線(xiàn)程對(duì)象添加到鎖關(guān)聯(lián)的隊(duì)列中,另一個(gè)線(xiàn)程釋放鎖時(shí)會(huì)檢查隊(duì)列中是否有線(xiàn)程對(duì)象,如果有則通知操作系統(tǒng)喚醒該線(xiàn)程

          因?yàn)樘幱诘却隣顟B(tài)的線(xiàn)程沒(méi)有運(yùn)行,即使長(zhǎng)時(shí)間不釋放也不會(huì)消耗 CPU
          資源,但讓線(xiàn)程進(jìn)入等待狀態(tài)與從等待狀態(tài)喚醒并調(diào)度運(yùn)行可能會(huì)花費(fèi)毫秒級(jí)的時(shí)間,與自旋鎖重試所需的納秒級(jí)時(shí)間相比非常的長(zhǎng)

          .NET 提供了 System.Threading.Mutex
          類(lèi),這個(gè)類(lèi)包裝了操作系統(tǒng)提供的互斥鎖,它是可重入的,已經(jīng)獲取鎖的線(xiàn)程可以再次執(zhí)行獲取蘇鎖的操作,但釋放鎖的操作也要執(zhí)行相同的次數(shù),可重入的鎖又叫遞歸鎖(Recursive
          Lock)

          遞歸鎖內(nèi)部使用一個(gè)計(jì)數(shù)器記錄進(jìn)入次數(shù),同一個(gè)線(xiàn)程每獲取一次就加1,釋放一次就減1,減1后如果計(jì)數(shù)器變?yōu)?就執(zhí)行真正的釋放操作,一般用在嵌套調(diào)用的多個(gè)函數(shù)中

          Mutex 類(lèi)的另一個(gè)特點(diǎn)是支持跨進(jìn)程使用,創(chuàng)建時(shí)通過(guò)構(gòu)造函數(shù)的第二個(gè)參數(shù)可以傳入名稱(chēng)


          如果一個(gè)進(jìn)程獲取了鎖,那么在釋放該鎖前的另一個(gè)進(jìn)程獲取同樣名稱(chēng)的鎖需要等待;如果進(jìn)程獲取了鎖,但是在退出之前沒(méi)有調(diào)用釋放鎖的方法,那么鎖會(huì)被操作系統(tǒng)自動(dòng)釋放,其他當(dāng)前正在等待鎖(鎖被自動(dòng)釋放前進(jìn)入等待狀態(tài))的進(jìn)程會(huì)收到
          AbandonedMutexException 異常

          跨進(jìn)程鎖通常用于保護(hù)多個(gè)進(jìn)程共享的資源或者防止程序多重啟動(dòng)

          混合鎖

          互斥鎖 Mutex 使用時(shí)必須創(chuàng)建改類(lèi)型的實(shí)例,因?yàn)閷?shí)例包含了非托管的互斥鎖對(duì)象,開(kāi)發(fā)者必須在不使用鎖后盡快調(diào)用 Dispose
          函數(shù)釋放非托管資源,并且因?yàn)楂@取鎖失敗后會(huì)立刻安排線(xiàn)程進(jìn)入等待,總體上性能比較低

          .NET 提供了更通用而且更高性能的混合鎖(Monitor),任何引用類(lèi)型的對(duì)象都可以作為鎖對(duì)象,不需要事先創(chuàng)建指定類(lèi)型的實(shí)例,并且涉及的非托管資源由
          .NET 運(yùn)行時(shí)自動(dòng)釋放,不需要手動(dòng)調(diào)用釋放函數(shù)

          獲取和釋放混合鎖需要使用 System.Threading.Monitor 類(lèi)中的函數(shù)

          C# 提供了 lock 語(yǔ)句來(lái)簡(jiǎn)化通過(guò) Monitor 類(lèi)獲取和釋放的代碼

          混合鎖的特征是在獲取鎖失敗后像自旋鎖一樣重試一定的次數(shù),超過(guò)一定次數(shù)之后(.NET Core 2.1 是30次)再安排當(dāng)前進(jìn)程進(jìn)入等待狀態(tài)


          混合鎖的好處是,如果第一次獲取鎖失敗,但其他線(xiàn)程馬上釋放了鎖,當(dāng)前線(xiàn)程在下一輪重試可以獲取成功,不需要執(zhí)行毫秒級(jí)的線(xiàn)程調(diào)度處理;而如果其他線(xiàn)程在短時(shí)間內(nèi)沒(méi)有釋放鎖,線(xiàn)程會(huì)在超過(guò)重試次數(shù)之后進(jìn)入等待狀態(tài),以避免消耗
          CPU 資源,因此混合鎖適用于大部分場(chǎng)景

          讀寫(xiě)鎖

          讀寫(xiě)鎖(ReaderWriterLock)是一個(gè)具有特殊用途的線(xiàn)程鎖,適用于頻繁讀取且讀取需要一定時(shí)間的場(chǎng)景


          共享資源的讀取操作通常是可以同時(shí)執(zhí)行的,而普通的互斥鎖不管是讀取還是修改操作都無(wú)法同時(shí)執(zhí)行,如果多個(gè)線(xiàn)程為了讀取操作而獲取互斥鎖,那么同一時(shí)間只有一個(gè)線(xiàn)程可以執(zhí)行讀取操作,在頻繁讀取的場(chǎng)景下會(huì)對(duì)吞吐量造成影響


          讀寫(xiě)鎖分為讀取鎖和寫(xiě)入鎖,線(xiàn)程可以根據(jù)對(duì)共享資源的操作類(lèi)型選擇獲取讀寫(xiě)鎖還是寫(xiě)入鎖,讀取鎖可以被多個(gè)線(xiàn)程同時(shí)獲取,寫(xiě)入鎖不可以被多個(gè)線(xiàn)程同時(shí)獲取,而且讀取鎖和寫(xiě)入鎖不可以被不同的線(xiàn)程同時(shí)獲取

          .NET 提供的 System.Threading.ReaderWriterLockSlim 類(lèi)實(shí)現(xiàn)了讀寫(xiě)鎖,

          讀寫(xiě)鎖也是一個(gè)混合鎖(Hybird Lock),在獲取鎖時(shí)通過(guò)自旋重試一定的次數(shù)再進(jìn)入等待狀態(tài)

          此外,它還支持同一個(gè)線(xiàn)程先獲取讀寫(xiě)鎖,然后再升級(jí)為寫(xiě)入鎖,適用于“需要先獲取讀寫(xiě)鎖,然后讀取共享數(shù)據(jù)判斷是否需要修改,需要修改時(shí)再獲取寫(xiě)入鎖”的場(chǎng)景

          參考資料

          《.NET Core 底層入門(mén)》

          如果閱讀文章后有所收獲,希望您可以點(diǎn)一個(gè)推薦,感覺(jué)不盡?。?!

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問(wèn)題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                美女被日 | 久久精品2021 | 午夜婷婷网 | 国产黄色片免费看 | 最新无码中文字幕 |