安全執行緒的元件
在執行緒之間共用資源是多執行緒程式設計常見的需求。 多個執行緒可能需要存取共用資料庫,或是對一組系統變數進行更新。 當一個以上的執行緒同時競相存取共用資源時,就可能發生競爭情形。 當一執行緒將資源修改為無效狀態,而接著另一執行緒嘗試存取這個資源並在無效狀態中使用它時,就出現競爭情形。 參考下列範例:
Public Class WidgetManipulator
Public TotalWidgets as Integer = 0
Public Sub AddWidget()
TotalWidgets += 1
Console.WriteLine("Total widgets = " & TotalWidgets.ToString)
End Sub
Public Sub RemoveWidgets()
TotalWidgets -= 10
End Sub
End Class
public class WidgetManipulator
{
public int TotalWidgets = 0;
public void AddWidget()
{
TotalWidgets++;
Console.WriteLine("Total widgets = " + TotalWidgets.ToString());
}
public void RemoveWidgets()
{
TotalWidgets -= 10;
}
}
這個類別顯露兩個方法。 第一個方法 AddWidget 會將 1 加入 TotalWidgets 欄位並將值寫入主控台 (Console)。 第二個方法會將 TotalWidgets 的值減去 10。 這時如果兩個執行緒同時嘗試存取 WidgetManipulator 類別的相同執行個體,請考慮一下會發生的情況。 一個執行緒可能在呼叫 AddWidget 的同時,第二個執行緒又呼叫 RemoveWidgets。 在這種情況下,在第一個執行緒報告正確值之前,TotalWidgets 的值就可能會被第二個執行緒變更。 這種競爭情形會導致報告錯誤的結果,而且可能導致資料損毀。
使用鎖定來避免競爭情形
您可使用鎖定來保護程式碼的關鍵區段 (Critical Section) 不受競爭情形影響。 由 Visual Basic 關鍵字 SyncLock 陳述式或是 C# 關鍵字 lock 陳述式表示的鎖定,允許單一執行緒取得某物件的獨佔執行權限。 以下範例將示範鎖定的用法:
SyncLock MyObject
' Insert code that affects MyObject.
End SyncLock
lock(MyObject)
{
// Insert code that affects MyObject.
}
當出現鎖定時,指定物件 (先前範例中的 MyObject) 上的執行就會被鎖定,直到執行緒可獨佔存取物件為止。 當到達鎖定結尾時,就會解除鎖定並照常繼續執行。 您只能鎖定傳回參考的物件。 您無法以這種方式鎖定實值型別 (Value Type)。
鎖定的缺點
雖然使用鎖定將保證多個執行緒不會同時存取一物件,但它們可能會明顯降低效能。 假設一程式有許多正在執行的不同執行緒。 如果每個執行緒都需要使用特定物件並且在執行之前需要等待取得這個物件的獨佔鎖定,那麼執行緒都將停止執行並在彼此背後相互阻擋,而使得效能的發揮十分有限。 綜合上述原因,您只應在具有必須以單一單位執行的程式碼時使用鎖定。 例如,您可能會要更新彼此相依的多個資源。 這種程式碼就稱為原子。 將鎖定的使用限制在只用於必須完整執行的程式碼,您就能夠寫入確保資料安全的多執行緒元件,同時還是能夠保持良好效能。
您也必須小心避免死結 (Deadlock) 可能發生的情況。 在此情況中,多個執行緒會彼此等待,以釋放共用資源。 例如,執行緒 1 可能鎖定在資源 A 上,並等待著資源 B。 另一方面,執行緒 2 可能鎖定在資源 B 上,並等待著資源 A。 在這種情況下,兩個執行緒都無法繼續。 避免死結情況的唯一方式就是透過仔細的程式設計。
請參閱
工作
逐步解說:使用 Visual Basic 撰寫簡單的多執行緒元件