進階同步技術
更新:2007 年 11 月
多執行緒應用程式通常使用等候控制代碼和監視物件,將多個執行緒同步。以下內容會說明如何在將執行緒同步化時使用下列的 .NET Framework 類別:AutoResetEvent、Interlocked、ManualResetEvent、Monitor、Mutex、ReaderWriterLock、Timer、WaitHandle。
等候控制代碼
等候控制代碼是一個物件,它將一個執行緒狀態的通知給另一個執行緒。執行緒可以使用等候控制代碼,通知其他的執行緒它們需要獨佔存取資源。然後其他執行緒必須等待使用這個資源,直到等候控制代碼不再使用為止。等候控制代碼有兩個狀態,信號和非信號。不屬於任何執行緒的等候控制代碼是信號狀態。屬於特定執行緒的等候控制代碼是非信號狀態。
執行緒會呼叫 WaitOne、WaitAny 或 WaitAll 其中一種等候方法,要求等候控制代碼的擁有權。等候方法是與個別執行緒的 Join 方法相似的封鎖呼叫:
如果沒有其他的執行緒擁有等候控制代碼,呼叫會立即傳回 True,等候控制代碼的狀態會變成未收到信號,而擁有等候控制代碼的執行緒則會繼續執行。
如果執行緒會呼叫某個等候控制代碼的等候方法,但該等候控制代碼是由另一個執行緒所擁有,則呼叫的執行緒會等候一段指定的時間 (如果已指定逾時),或是無限期等候其他執行緒釋放此等候控制碼 (如果沒有指定逾時)。如果已指定逾時且等候控制代碼會在逾時到期前被釋放,則呼叫會傳回 True。否則,要等候的呼叫會傳回 False,而呼叫執行緒會繼續執行。
當擁有等候控制代碼的執行緒完成或不再需要等候控制代碼時,這些執行緒會呼叫 Set 方法。其他執行緒可以呼叫 Reset 方法,或呼叫 WaitOne、WaitAny 或 WaitAll 並順利等候執行緒呼叫 Set,藉以重設等候控制代碼的狀態。系統會在釋放單一等候中的執行緒之後,自動將 AutoResetEvent 控制代碼重設為未收到信號。如果沒有等候中的執行緒,事件物件 (Event Object) 會維持收到信號的狀態。
通常會有三種等候控制代碼與 Visual Basic 搭配使用:mutex 物件、ManualResetEvent 及 AutoResetEvent。最後兩個控制代碼通常稱為同步事件。
Mutex 物件
Mutex 物件是同步物件 (Synchronization Object),一次只能屬於一個執行緒。而 "Mutex" 這個名稱即表示 Mutex 物件的擁有權是互斥 (Mutually Exclusive) 的。當執行緒需要獨佔存取資源時,執行緒會要求 Mutex 物件的擁有權。因為同時只能有一個執行緒擁有 Mutex 物件,所以其他的執行緒在使用資源之前,必須等待取得 Mutex 物件的擁有權。
WaitOne 方法會讓呼叫執行緒等候 Mutex 物件的擁有權。如果執行緒在擁有 Mutex 物件時正常終止,則 Mutex 物件的狀態設定為信號,且下一個等候中的執行緒會取得擁有權。
同步事件
同步事件會通知其他執行緒已有作業發生或是有資源可供使用。儘管這個詞彙中會包含 "event" 這個字,但同步事件與其他的 Visual Basic 事件不同,它們實際上會等候控制代碼。如同其他的等候控制代碼,同步事件有兩個狀態,信號和非信號。
呼叫同步事件等候方法的執行緒必須等待,直到另一個執行緒經由呼叫 Set 方法通知事件為止。同步事件類別有兩個:ManualResetEvent 和 AutoResetEvent。
執行緒會使用 Set 方法,將 ManualResetEvent 執行個體的狀態設定為收到信號。執行緒會使用 Reset 方法,或是在控制回到等候的 WaitOne 呼叫時,將 ManualResetEvent 執行個體的狀態設定為未收到信號。
您也可以使用 Set,將 AutoResetEvent 類別的執行個體設定為收到信號,但只要等候中的執行緒接到事件變成收到信號的通知,這些執行個體就會自動變回未收到信號。
監視物件和 SyncLock
監視物件用來確定程式碼區塊執行順利,而不會被在其他執行緒上執行的程式碼中斷。換句話說,其他執行緒內的程式碼在同步程式碼區塊中的程式碼完成之前是無法執行的。
例如,假設您有一個程式會不斷的非同步讀取資料並顯示結果。在使用優先式多工作業系統時,正在執行的執行緒會被作業系統中斷,讓其他執行緒有時間執行。若沒有同步化作業,如果代表資料的物件在顯示資料時被另一個執行緒修改,則您可能得到資料部分的更新檢視。監視物件保證程式碼的區段不會在執行時中斷,Visual Basic 提供 SyncLock 與 End SyncLock 陳述式 (Statement),簡化監視物件的存取權限。Visual C# 會以相同的方式使用 Lock 關鍵字。
注意事項: |
---|
只有當存取程式碼包含於相同物件執行個體上的 SyncLock 區塊內時,才會鎖定物件的存取權限。 |
如需 SyncLock 陳述式的資訊,請參閱 SyncLock 陳述式。
Interlocked 類別
您可以使用 Interlocked 類別的方法,防止在多執行緒嘗試同時更新或比較相同值時發生問題。這個類別的方法讓您能夠安全的遞增、遞減、交換和比較任何執行緒的值。下列範例會顯示如何使用 Increment 方法,將不同執行緒上執行之程序所共用的變數進行遞增。
Sub ThreadA(ByRef IntA As Integer)
System.Threading.Interlocked.Increment(IntA)
End Sub
Sub ThreadB(ByRef IntA As Integer)
System.Threading.Interlocked.Increment(IntA)
End Sub
ReaderWriter 鎖定
在某些狀況下,您可能只要在寫入資料時鎖定資源,並允許多個用戶端在資料沒有更新時同時讀取資料。ReaderWriterLock 類別會在執行緒修改資源時強制獨佔存取資源,但在讀取資源時則允許非獨佔存取。ReaderWriter 鎖定是獨佔鎖定的替代方法,這個方法非常有用,因為獨佔鎖定會讓其他的執行緒等待,即使這些執行緒不需要更新資料。
死結
執行緒同步在多執行緒應用程式 (Multithreaded Application) 中非常重要,但建立 deadlock 一向很危險,因為其中會有多個執行緒互相等候彼此,因而使應用程式中止。死結的情況類似車子停靠在有四個方向的停車站,而且每個人都要等候另一個人才能出發。避免發生死結是很重要的,關鍵則在於要小心規劃。在開始寫程式前先圖解多執行緒應用程式,通常都可以預測到死結狀況。