1. 程式人生 > >.net 執行緒 和 執行緒安全

.net 執行緒 和 執行緒安全

第一節 使用執行緒和執行緒處理

啟動時建立執行緒並傳遞資料

publicclassThreadWithState{ privatestring boilerplate; privateint value; publicThreadWithState(string text,int number) {         boilerplate = text;         value = number; } publicvoidThreadProc() { Console.WriteLine(boilerplate, value); } }

Thread t =newThread(newThreadStart

(tws.ThreadProc)); t.Start(); Console.WriteLine("Main thread does some work, then waits."); t.Join();//等待執行緒join結束 才開始當前執行緒

暫停和繼續執行緒

同步執行緒活動的最常用方法是鎖定和釋放執行緒,或者鎖定物件或程式碼區域。

還可以讓執行緒將自身置於休眠狀態

呼叫 System.Threading.Thread.Sleep 及 System.Threading.Thread.Infinite 將使執行緒休眠,直到被呼叫 System.Threading.Thread.Interrupt 的另一個執行緒中斷,或被System.Threading.Thread.Abort 終止。System.Threading.Thread.Interrupt 會引發 ThreadInterruptedException,可以中斷正在等待的執行緒,從而使該執行緒脫離造成阻止的呼叫

與 System.Threading.Thread.Sleep 不同,System.Threading.Thread.Suspend 不會導致執行緒立即停止執行。公共語言執行庫必須一直等待,直到執行緒到達安全點之後它才可以將該執行緒掛起, 直到被System.Threading.Thread.Resume喚醒。如果執行緒尚未啟動或已經停止,則它將不能掛起.  

銷燬執行緒

呼叫Abort中斷執行緒引發ThreadAbortException異常,這是一種可捕獲的特殊異常,但在 catch 塊的結尾處它將自動被再次引發。引發此異常時,執行庫將在結束執行緒前執行所有 finally 塊。由於執行緒可以在 finally 塊中執行未繫結計算,或呼叫 Thread.ResetAbort來取消中止,所以不能保證執行緒將完全結束。

如果您希望一直等到被中止的執行緒結束,可以呼叫 Thread.Join 方法。Join 是一個模組化呼叫,它直到執行緒實際停止執行時才返回。

銷燬執行緒

每個執行緒都具有分配給它的執行緒優先順序。為在公共語言執行庫中建立的執行緒最初分配的優先順序為ThreadPriority.Normal。在執行庫外建立的執行緒會保留它們在進入托管環境之前所具有的優先順序。您可以使用Thread.Priority 屬性獲取或設定任何執行緒的優先順序

第二節 前臺和後臺執行緒

.Net的公用語言執行時能區分兩種不同型別的執行緒:前臺執行緒和後臺執行緒。這兩者的區別就是:應用程式必須執行完所有的前臺執行緒(包括正常退出和異常退出)後才可以退出;而對於後臺執行緒,應用程式則可以不考慮其是否已經執行完畢而直接退出,所有的後臺執行緒在應用程式退出時都會自動結束

.net環境使用Thread建立的執行緒預設情況下是前臺執行緒,即執行緒屬性IsBackground=true,在程序中,只要有一個前臺執行緒未退出,程序就不會終止。主執行緒就是一個前臺執行緒。而後臺執行緒不管執行緒是否結束,只要所有的前臺執行緒都退出(包括正常退出和異常退出)後,程序就會自動終止。一般後臺執行緒用於處理時間較短的任務,如在一個Web伺服器中可以利用後臺執行緒來處理客戶端發過來的請求資訊。而前臺執行緒一般用於處理需要長時間等待的任務,如在Web伺服器中的監聽客戶端請求的程式,或是定時對某些系統資源進行掃描的程式。下面的程式碼演示了前臺和後臺執行緒的區別。

第三節 為多執行緒處理同步資料

當多個執行緒可以呼叫單個物件的屬性和方法時,對這些呼叫進行同步處理是非常重要的。否則,一個執行緒可能會中斷另一個執行緒正在執行的任務,使該物件處於一種無效狀態。其成員不受這類中斷影響的類叫做執行緒安全類。

“公共語言基礎結構”提供了幾種可用來同步對例項和靜態成員的訪問的策略:

同步程式碼區域。可以使用 Monitor 類或該類的編譯器支援來僅同步需要該類的程式碼塊,從而改善效能。

手動同步。可以使用 .NET Framework 類庫提供的同步物件。請參見 同步基元概述,這部分對 Monitor 類進行了討論。

同步上下文。可以使用 SynchronizationAttribute 為 ContextBoundObject 物件啟用簡單的自動同步。

Synchronized 屬性。Hashtable 和 Queue 等幾個類提供了一個可為該類的例項返回執行緒安全包裝的 Synchronized屬性。請參見集合和同步(執行緒安全)。

 
Monitor [獨佔鎖] [執行緒關聯] [跨程序]                          

Monitor物件通過使用 Monitor.Enter、Monitor.TryEnter 和 Monitor.Exit 方法對特定物件獲取鎖和釋放鎖來公開同步訪問程式碼區域的能力。在對程式碼區域獲取鎖後,就可以使用 Monitor.Wait、Monitor.Pulse 和 Monitor.PulseAll 方法了。如果鎖被暫掛,則 Wait 釋放該鎖並等待通知。當 Wait 接到通知後,它將返回並再次獲取該鎖。Pulse 和 PulseAll 都會發出訊號以便等待佇列中的下一個執行緒繼續執行。

Monitor 將鎖定物件(即引用型別),而非值型別。儘管可以向 Enter 和 Exit 傳遞值型別,但對於每次呼叫它都是分別裝箱的。因為每次呼叫都建立一個獨立的物件,所以 這樣做的結果就是Enter 永遠不會阻止,而且它要保護的程式碼並沒有真正同步。另外,傳遞給 Exit 的物件不同於傳遞給 Enter 的物件,所以 Monitor 將引發 SynchronizationLockException,並顯示以下訊息:“從不同步的程式碼塊中呼叫了物件同步方法。”下面的示例演示這些問題。

意到 Monitor 和 WaitHandle 物件在使用上的區別是非常重要的。Monitor 物件是完全託管、完全可移植的,並且在作業系統資源要求方面可能更為有效。WaitHandle 物件表示作業系統可等待物件,對於在託管和非託管程式碼之間進行同步非常有用,並公開一些高階作業系統功能(如同時等待許多物件的能力)。


WaitHandle [訊號量和其他等待控制代碼的抽象基類] [跨程序]

等同於C#中的lock(object)語法

WaitHandle 類本身是抽象類。除派生類之外,它還具有許多對多個事件啟用等待的靜態方法。從 WaitHandle派生的類包括Mutex 類, EventWaitHandle 類及其派生類、AutoResetEvent 和 ManualResetEvent, Semaphore 類.

由於 WaitHandle 類派生自 MarshalByRefObject,所以這些類可用於跨應用程式域邊界同步執行緒的活動。

執行緒可以通過呼叫例項方法 WaitOne 在單個等待控制代碼上阻塞。此外,WaitHandle 類過載了靜態方法,以等待所有指定的等待控制代碼集都已收到訊號 (WaitAll),或等待某一指定的等待控制代碼集收到訊號 (WaitAny)。這些方法的過載提供了放棄等待的超時間隔、在進入等待之前退出同步上下文的機會,並允許其他執行緒使用同步上下文。

在 .NET Framework 2.0 版中,等待控制代碼也具有靜態 SignalAndWait方法,該方法允許執行緒傳送一個等待控制代碼訊號,然後立即等待另一個等待控制代碼,如同原子操作一樣。

WaitHandle 的派生類具有不同的執行緒關聯。事件等待控制代碼(EventWaitHandle、AutoResetEvent 和 ManualResetEvent)以及訊號量沒有執行緒關聯任何執行緒都可以傳送事件等待控制代碼或訊號量的訊號。另一方面,mutex 具有執行緒關聯。擁有 mutex 的執行緒必須將其釋放;

Mutex 互斥鎖  [獨佔鎖] [執行緒關聯] [跨程序]

Mutex 類比 Monitor 類使用更多系統資源,但是它可以跨應用程式域邊界進行封送處理,可用於多個等待,並且可用於同步不同程序中的執行緒. 執行緒呼叫 mutex 的 WaitOne 方法請求所有權。該呼叫會一直阻塞到 mutex 可用,或直至達到可選的超時間隔。如果沒有任何執行緒擁有它,則 Mutex 的狀態為已發訊號的狀態。執行緒通過呼叫其 ReleaseMutex 方法釋放 mutex。mutex 具有執行緒關聯;即 mutex 只能由擁有它的執行緒釋放,否則會在該執行緒中引發 ApplicationException。

由於 Mutex 類從 WaitHandle 派生,所以您還可以結合其他等待控制代碼呼叫 WaitHandle 的靜態 WaitAll 或 WaitAny 方法請求 Mutex 的所有權。 如果某個執行緒擁有 Mutex,則該執行緒就可以在重複的等待-請求呼叫中指定同一個 Mutex,而不必阻止其執行;但是,它必須釋放 Mutex,次數與釋放所屬權的次數相同

Mutex 分兩種型別:本地 mutex 和命名系統 mutex。如果使用接受名稱的建構函式建立了 Mutex 物件,那麼該物件將與具有該名稱的作業系統物件相關聯。命名的系統 mutex 在整個作業系統中都可見,並且可用於同步程序活動。您可以建立多個 Mutex 物件來表示同一命名系統 mutex,而且您可以使用 OpenExisting 方法開啟現有的命名系統 mutex。本地 mutex 僅存在於程序當中。程序中引用本地 Mutex 物件的任意執行緒都可以使用本地 mutex。 

EventWaitHandle、AutoResetEvent 和 ManualResetEvent [非獨佔鎖] [非執行緒關聯] [跨程序]

        事件等待控制代碼允許執行緒通過彼此傳送訊號和等待彼此的訊號來同步活動。這些同步事件是基於 Win32 等待控制代碼的,可分為兩種型別:一種收到訊號時自動重置;另一種需手動重置。 事件等待控制代碼在與 Monitor 類相同的許多同步情況下十分有用。事件等待控制代碼通常比使用 System.Threading.Monitor.Wait和 System.Threading.Monitor.Pulse(System.Object)方法更簡單,並且可以對訊號傳送提供更多控制。命名事件等待控制代碼也可用於跨應用程式域和程序同步活動,而監視器對於應用程式域是本地的。

Interlocked 互鎖操作 [聯鎖] [高效能] [可跨程序]

        Interlocked 類提供這樣一些方法,即同步對多個執行緒共享的變數的訪問的方法。如果該變數位於共享記憶體中,則不同程序的執行緒就可以使用該機制。互鎖操作是原子的 — 即整個操作是不能由相同變數上的另一個互鎖操作所中斷的單元。這在搶先多執行緒作業系統中是很重要的,在這樣的作業系統中,執行緒可以在從某個記憶體地址載入值之後但是在有機會更改和儲存該值之前被掛起。在現代處理器中,Interlocked 類的方法經常可以由單個指令來實現。因此,它們提供效能非常高的同步,並且可用於構建更高階的同步機制,例如自旋鎖。

Interlocked 類提供了以下操作:

Add方法向變數新增一個整數值並返回該變數的新值。

Read方法作為一個原子操作讀取一個 64 位整數值。

Increment 和 Decrement方法遞增或遞減某個變數並返回結果值。

Exchange 方法執行指定的變數中的值的原子交換,返回該值並將其替換為新值。可以過載在引用型別的變數上執行此交換。

CompareExchange 方法也交換兩個值,但是視比較的結果而定。


ReaderWriterLock [讀寫鎖] [執行緒關聯]

允許多個執行緒同時讀取一個資源,但在向該資源寫入時要求執行緒等待獲得獨佔鎖。 在應用程式中,可以使用 ReaderWriterLock 在訪問一個共享資源的執行緒之間提供協調同步。在這種情況下,獲得的鎖是針對 ReaderWriterLock 本身的。與任何執行緒同步機制相同,您必須確保任何執行緒都不會跳過 ReaderWriterLock。或者,也可以設計一個封裝資源的類。此類可以使用 ReaderWriterLock 實現其針對資源的鎖定方案。ReaderWriterLock 使用了一種高效的設計,可用來同步單個物件。設計您應用程式的結構,讓讀取和寫入的時間儘可能最短。因為寫入鎖定是排他的,所以長時間的寫入會直接降低吞吐量。長時間的讀取會阻止處於等待的編寫器,並且,如果至少有一個執行緒在等待寫入鎖,那麼請求新的讀取器鎖的執行緒也將被阻止。

訊號量 [生產者消費者鎖] [非執行緒關聯] [跨程序]

Semaphore 類表示一個命名訊號量(系統範圍)或本地訊號量。Windows 訊號量是計數訊號量,可用於控制對資源池的訪問。執行緒通過呼叫 WaitOne 方法來進入訊號量,此方法是從 WaitHandle類派生的。當呼叫返回時,訊號量的計數將減少。當一個執行緒請求項而計數為零時,該執行緒會被阻止。當執行緒通過呼叫 Release 方法釋放訊號量時,將允許被阻止的執行緒進入。針對讓被阻止的執行緒進入訊號量,不存在保證的順序(例如 FIFO 或 LIFO)。執行緒可以通過重複呼叫 WaitOne 方法來多次進入訊號量。若要釋放訊號量,執行緒可以呼叫 Release 方法過載相同的次數,也可以呼叫 Release 方法過載並指定要釋放的項數

Windows 作業系統允許訊號量具有名稱。命名訊號量在整個系統範圍都有效。即,建立命名訊號量後,所有程序中的所有執行緒都是可見的。因此,命名訊號量可用於同步程序的活動以及執行緒的活動。

訊號量的一個常用方案包括一個生產者執行緒和一個使用者執行緒,其中一個執行緒總是增加訊號量計數,而另一個執行緒總是減少訊號量計數。因此他不是執行緒關聯的

SpinLock 自旋鎖 [獨佔鎖] [執行緒關聯]

嘗試獲取該鎖的執行緒持續不斷的check是否可以獲得。此時執行緒仍然是啟用狀態,只是在空轉,浪費cpu而已。單核是不建議使用自旋的. 但是spinlock避免了執行緒排程和上下文切換,如果鎖的時間極短的話,使用該鎖反而效率會高。

而lock[Monitor]是執行緒被block了。這將引起執行緒排程和上下文切換等行為。

Spinlock在自旋極短的時間內是可以採取的。

不能呼叫Enter兩次在同一個spinlock上面。

SpinLock允許你查詢是否鎖已經被其他執行緒佔用,通過IsHeld屬性。


 執行緒關聯定義: 進入Lock的執行緒必須通過自身呼叫 Exit 或 Wait 才能退出。

執行緒安全

線上程中使用共享資源時,能夠保證共享資源在任何時候都是原子的、一致的,這樣的執行緒就是執行緒安全的執行緒, 一個類要成為執行緒安全的類,就是在該類被多個執行緒訪問時,不管執行環境中執行這些執行緒有什麼樣的時序安排或者交錯,它仍然執行正確行為,並且在呼叫的程式碼中沒有任何額外的同步。

當一個類的例項為singleton的時候,你就要考慮該例項在呼叫的時候是否是執行緒安全的