.NET Framework 提供了一系列同步基元來控制線程交互並避免爭用條件。這可大致分爲三個類別:鎖定、通知和聯鎖操作。
上述類別的定義並非是絕對的:有些同步機制具有多個類別的特徵;一次釋放一個線程的事件的功能類似於鎖定;任何鎖定的釋放都可看作一個信號;而聯鎖操作可用於構造鎖定。但是,這些類別仍然是有用的。
記住線程同步是協作這一點非常重要。只要有一個線程避開同步機制直接訪問受保護的資源,該同步機制就不是有效的。
鎖定
鎖向一個線程一次提供一個資源的控制功能,或者向指定數目的線程提供此功能。請求正在使用中的獨佔鎖的線程會被阻止,直到該鎖變爲可用爲止。
獨佔鎖
鎖定的最簡單的形式是 C# 的 lock 語句(在 Visual Basic 中爲 SyncLock),該語句可控制對代碼塊的訪問。這種塊通常稱爲臨界區。lock 語句使通過使用 Monitor 類的 Enter 和 Exit 方法實現的,它使用 try…catch…finally 確保該鎖被釋放。
通常情況下,使用 lock 語句保護小代碼塊並且不跨越多個方法是使用 Monitor 類的最佳方法。Monitor 類功能強大,但是容易形成孤立鎖和死鎖。
Monitor 類
Monitor 類提供了附加功能,可結合 lock 語句使用:
-
TryEnter 方法允許當前被阻止,正在等待資源的線程在指定時間間隔之後放棄。它返回一個指示成功或失敗的布爾值,可用於檢測和避免潛在的死鎖。
-
Wait 方法由臨界區中的線程調用。它放棄對資源的控制並阻止,直到該資源重新可用爲止。
-
Pulse 和 PulseAll 方法允許要釋放鎖或調用 Wait 的線程將一個或多個線程放入就緒隊列,以使它們能夠獲取鎖。
Wait 方法重載的超時允許等待線程進入就緒隊列。
如果用於鎖的對象派生自 MarshalByRefObject,則 Monitor 類可在多個應用程序域中提供鎖定。
Monitor 具有線程關聯。也就是說,進入監視器的線程必須通過調用 Exit 或 Wait 才能退出。
Monitor 類不可實例化。其方法是靜態(在 Visual Basic 中爲 Shared)方法,用於可實例化的鎖對象。
有關概念性概述,請參見監視器。
Mutex 類
線程通過調用其 WaitOne 方法的重載請求 Mutex。提供了具有超時的重載,以便允許線程放棄等待。與 Monitor 類不同,mutex 可以是局部的,也可以是全局的。全局 mutex(也稱爲命名的 mutex)在整個操作系統中可見,可用於在多個應用程序域或進程中同步線程。局部 mutex 派生自 MarshalByRefObject,可以跨應用程序域邊界使用。
此外,Mutex 派生自 WaitHandle,這意味着它可用於 WaitHandle 提供的通知機制,如 WaitAll、WaitAny 和 SignalAndWait 方法。
與 Monitor 一樣,Mutex 具有線程關聯。與 Monitor 不同,Mutex 是可實例化的對象。
有關概念性概述,請參見 Mutex。
其他鎖
鎖定不必是獨佔的。允許有限數目的線程併發訪問某個資源通常十分有用。信號量和讀寫器鎖旨在控制此類池資源訪問。
ReaderWriterLock 類
ReaderWriterLock 類用於更改數據的線程(編寫器)必須獨佔訪問某個資源的情形。如果編寫器不是活動的,則任何數量的讀取器均可通過調用 AcquireReaderLock 方法訪問該資源。一旦有線程調用 AcquireWriterLock 方法,後續讀取器請求則會阻止,直到所有現有讀取器均釋放了該鎖,並且有一個編寫器獲取並釋放了該鎖爲止。
ReaderWriterLock 具有線程關聯。
有關概念性概述,請參見讀取器/編寫器鎖。
Semaphore 類
通知
等待另一個線程的信號的最簡單方法是調用 Join 方法,該方法會一直阻止,直到另一個線程完成爲止。Join 有兩個重載方法,這兩個方法允許阻止的線程在等待指定時間間隔之後跳出等待。
等待句柄提供了更爲豐富的等待和通知功能。
等待句柄
等待句柄派生自 WaitHandle 類,後者又派生自 MarshalByRefObject。因此,等待句柄可用於跨應用程序域邊界同步線程的活動。
通過調用實例方法 WaitOne 或者靜態方法 WaitAll、WaitAny 或 SignalAndWait 中的一個方法,線程可由等待句柄阻止。它們的釋放方式取決於調用的方法以及等待句柄的種類。
有關概念性概述,請參見等待句柄。
事件等待句柄
事件等待句柄包括 EventWaitHandle 類及其派生類 AutoResetEvent 和 ManualResetEvent。當通過調用 Set 方法或使用 SignalAndWait 方法通知事件等待句柄時,線程會從事件等待句柄釋放。
事件等待句柄要麼自動重置自身(類似於每次得到通知時只允許一個線程通過的旋轉門),要麼必須手動重置(類似於在通知前一直關閉,有人將其關閉前則一直打開的大門)。顧名思義,AutoResetEvent 和 ManualResetEvent 分別表示前者和後者。
EventWaitHandle 可表示這兩種類型的事件,並且既可以是局部的也可以是全局的。派生類 AutoResetEvent 和 ManualResetEvent 始終是局部的。
事件等待句柄不具有線程關聯。任何線程都可以通知事件等待句柄。
有關概念性概述,請參見 EventWaitHandle、AutoResetEvent 和 ManualResetEvent。
Mutex 和 Semaphore 類
因爲 Mutex 和 Semaphore 類派生自 WaitHandle,所以它們可用於 WaitHandle 的靜態方法。例如,線程可以使用 WaitAll 方法等待,直到滿足以下三個條件爲止:EventWaitHandle 接收到通知,Mutex 已釋放,Semaphore 已釋放。類似地,線程可以使用 WaitAny 方法等待,直到滿足上述所有條件爲止。
對於 Mutex 或 Semaphore,接收到通知即意味着被釋放。如果上述兩個類型之一用作 SignalAndWait 方法的第一個參數,該類型即被釋放。對於具有線程關聯的 Mutex,如果進行調用的線程不具有該 mutex,則會引發異常。如前所述,信號量不具有線程關聯。
聯鎖操作
聯鎖操作是由 Interlocked 類的靜態方法對某個內存位置執行的簡單原子操作。這些原子操作包括添加、遞增和遞減、交換、依賴於比較的條件交換,以及 32 位平臺上的 64 位值的讀取操作。
注意 |
---|
原子性的保證僅限於單個操作;如果必須將多個操作作爲一個單元執行,則必須使用更粗粒度的同步機制。 |
儘管這些操作中沒有一個是鎖或信號,但它們可用於構造鎖和信號。因爲它們是 Windows 操作系統固有的,因此聯鎖操作的執行速度非常快。
聯鎖操作可用於可變內存保證,以編寫展示功能強大的非阻止併發的應用程序,但是,它們需要複雜的低級別編程,因此大多數情況下簡單鎖是更好的選擇。
有關概念性概述,請參見互鎖操作。