執行緒同步的情景之一

2021-09-06 19:00:02 字數 3780 閱讀 3182

從本篇文章開始,我將陸續介紹多執行緒中會遇到的三種情況。 

情景一:此茅坑有主了

大錘:「我擦,居然乙個茅坑有兩個人在用。」

大錘:「啊,忍不住了,一起擠擠吧~~~」

叫獸:「舒坦了,先走了。」

叫獸按下了沖水開關.... "嘩啦啦....."

大錘:「你妹啊,衝什麼水啊,衝得我一身 **** 」

解決方案:為了解決這種混亂的情況,管理員給茅坑加了道門,一次只允許乙個人使用,其他人只能在外面等待。而且只要有人佔著,就算不拉屎,其他人也只能乖乖排隊。

問題抽象:當某一資源可能同時被多個執行緒讀取和修改時,資源的狀態將變得難以預料。

執行緒同步方案:interlocked、lock、moniter、spinlock、readwritelockslim、mutex

方案特性:除所有者外,其他人無條件等待;先到先得(誰先進茅坑,誰先用,沒有先後順序)

各方案間的區別(關於如何使用每種方案,很多文章和書籍都有介紹,就不再一一贅述了。)

這些方案從它們各自的實現方式可分為三種:使用者模式構造、核心模式構造 和 混合模式構造。

應該盡量使用使用者模式構造,它們的速度要顯著快於核心模式的構造。這是因為它們使用了特殊 cpu 指令來協調執行緒。這意味著協調是在硬體中發生的(所以才這麼快)。它們有乙個缺點:只有 windows 作業系統核心才能停止乙個執行緒的執行(以避免浪費 cpu 時間)。所以,乙個執行緒想要取得乙個資源但又暫時取不到,它會一直在使用者模式中執行。這可能浪費大量 cpu 時間。

核心模式的構造是由 windows 作業系統自身提供的。所以,它們要求你在應用程式的執行緒中呼叫在作業系統核心中實現的函式。將執行緒從使用者模式切換為核心模式(或相反)會招致巨大的效能損失,這正是為什麼應該避免使用核心模式構造的原因。然後,它們有乙個重要的優點:乙個執行緒使用乙個核心模式的構造獲取乙個由其它執行緒擁有的資源時,windows會阻塞執行緒,使它不再浪費cpu 時間。然後,當資源變得可用時,windows 會恢復執行緒,允許它訪問資源。

---- 《clr via c# (第 3 版)》 p706

上面這段話摘自《clr via c#》,各別用詞稍微調整了下以便於理解。簡單來說,使用者模式會通過在 cpu 中不斷的執行某些指令來達到阻塞執行緒的效果(想像一下一直執行 while(true); 的樣子),而核心模式則是實實在在的把執行緒的執行給停止了,cpu 不會再去排程這個執行緒。混合模式,就不用說了,是兩者的結合。

那什麼時候該用什麼模式的構造呢?對於短時間的阻塞,選擇使用者模式;長時間的阻塞,選擇核心模式;阻塞時間不定的,選擇混合模式。

interlocked保證的是原子性,其原子操作包括 「遞增」、「遞減」、「相加」、「交換」 。之所以把它也歸入情景一,是因為它通過原子操作確保乙個資源在 「讀取後,寫入前」 不會有其它執行緒中斷它的執行,從而保證了資源的獨佔使用。

優點:速度最快,且單次操作阻塞時間短。

缺點:可執行的操作有限。

spinlock自旋鎖,在 .net 4.0 的時候引入。自旋的意思就是自個兒在原地旋轉,以此來占用 cpu 時間。說白了就是類似 「while(狀態是否可用); 」,如果狀態不可用,則一直迴圈,直到狀態可用為止。可以用 interlocked 來實現 spinlock 的效果:

//

參考 clr via c#

struct

myspinlock

public

void

exit()

}

優點:速度快,可以用於各種操作。

缺點:如果操作需要很長時間,將會嚴重浪費 cpu 時間。在單核的處理器中使用該方式,可能造成死鎖。因為如果加鎖的執行緒優先順序低於阻塞的執行緒,那可能很長一段時間都無法被排程到cpu上,這樣就無法解鎖。

mutex可以跨程序保證資源的獨佔使用,通過 waitone 來獲取鎖,releasemutex 釋放鎖(使用哪個執行緒執行的 waitone,只能由該執行緒 releasemutex)。它與後面要講到的 「event」 都來自於同乙個父類 waithandle。這是乙個抽象類,包裝了 windows 作業系統的核心物件控制代碼。

主要用於:限制應用程式只能啟動一次。如 sql server、360安全衛士。

**示例:

[stathread]

static

void

main()

else

}

優點:允許遞迴使用,可以跨程序使用

缺點:速度最慢(不僅是因為會在核心模式與使用者模式間進行切換,造成效能的損失;也因為相對於 event,它提供了遞迴使用等高階的功能,這導致它比其它結構都要複雜)

moniter方式通過呼叫靜態方法 enter、exit 來實現對共享資源或**段的獨佔使用,是 .net 領域中問世最早的一種執行緒同步機制。我們都知道每個引用型別在堆中都會包含兩個特殊的字段:同步塊索引 和 型別物件指標。而使用 moniter.enter 實際就會去操作同步塊索引,讓它指向堆中的同步塊陣列;mointer.exit 則會重新將同步塊索引置為 -1。

優點:速度還行,介於核心模式和使用者模式之間;支援遞迴使用。

缺點:會把所有操作(讀或寫)該資源的執行緒都阻塞,而當系統中讀執行緒的數量遠遠多於寫執行緒的時候,很有可能出現同一時刻只有多個讀執行緒,這個時候阻塞的行為就顯得多餘了。

lock是 c# 的語法糖,通過檢視 il **可以知道,它最終將被解釋為 moniter.enter 和 moniter.exit。下面是c# 4.0**的 il。

通過上面的 il,可以明確的看到 moniter.exit 被放置在 finally 塊中,這樣保證了鎖最終將被正確釋放(避免了可能發生的死鎖)。但有一點值得注意的是,如果**塊中丟擲了異常,儘管可以保證鎖被釋放,但無法保證其中的共享資源仍舊是正確的

優點:使用簡單;保證鎖肯定會被釋放;速度同 moniter 。

缺點:同 moniter。

readwritelockslim與 mointer 不同,它通過 enterreadlock、enterwritelock、exitreadlock、exitwritelock 來區別對待讀執行緒還是寫執行緒。所以對於讀執行緒加讀鎖,而寫執行緒加寫鎖,這樣當當前時刻不存在寫執行緒的時候,所有讀執行緒都可以併發的訪問資源。

優點:讀、寫鎖分離。當不存在寫執行緒的時候,速度要明顯快於 mointer。而當有寫執行緒的時候,速度稍慢於 mointer。

上面的方式各有優缺點,就算是經驗豐富的程式猿也不一定能保證執行緒一定是安全的。所以只要有可能還是建議大家盡量不使用、少使用共享資源,或者讓共享資源變成唯讀

情景一中所說的所有方法都是圍繞乙個目的 ------ 「解決對共享資源的爭用問題」。當在實際開發過程中,如果碰到了共享資源(靜態變數、型別的成員變數、檔案等)或需要獨佔使用的**段時,請考慮採用上述方式中的任何一種來保證執行緒安全。

執行緒同步的情景之三

在情景 一 情景二中,我分別介紹了當多執行緒遇到 資源爭用 限量使用 情形時的解決方案,本篇是本系列的最後一種情形,會介紹幾種用於解決執行緒通訊的方案。情景三 我讓你動,你才能動!大錘 老闆,拿這個手機讓我看看 大錘 這是手機嗎?分別就只是乙個殼子 老闆 呀,這可能是生產上出了問題,我給你換乙個!大...

C 執行緒同步的三類情景分析

c 已經提供了我們幾種非常好用的類庫如 backgroundworker thread task等,借助它們,我們就能夠分分鐘編寫出乙個多執行緒的應用程式。比如這樣乙個需求 有乙個 winform 窗體,點選按鈕後,會將窗體中的資料匯出到乙個 output.pdf 檔案中。原先的 沒有採用多執行緒技...

策略模式應用情景之一

一直感覺設計模式很抽象,看到的例子也是僅僅列出不可缺少的部分,如果應用到真實場景,很難聯絡起來,如果是僅僅不可缺少的部分,特別是對習慣用spring的來說,放到容器裡面獲取使用不是更方便?下面舉一例實際開發中的場景。不多說了,就說和其他例子不一樣的部分,findconfiguretion和excut...