C# 线程问题之争用条件
用多個線程編程并不容易。在啟動訪問相同數據的多個線程時,會間歇性地遇到難以發現的問題。如果使用任務、并行 LINQ 或 Parallel 類,也會遇到這些問題。為了避免這些問題,必須特別注意同步問題和多個線程可能發生的其他問題。下面探討與線程相關的問題爭用條件。
ThreadingIssues示例的代碼使用了如下名稱空間:?
System.Diagnostics?
System.Threading
System.Threading.Tasks
static System.Console
可以使用命令行參數啟動 ThreadingIssues示例應用程序,來模擬爭用條件。
如果兩個或多個線程訪問相同的對象,并且對共享狀態的訪問沒有同步,就會出現爭用條件。為了說明爭用條件,下面的例子定義一個 StateObject 類,它包含一個 int 字段和一個 ChangeState() 方法。在 ChangeState() 方法的實現代碼中,驗證狀態變量是否包含5。如果它包含,就遞增其值。下一條語句是Trace.Assert,它立刻驗證 state 現在是包含 6。
在給包含 5 的變量遞增了 1 后,可能認為該變量的值就是 6。但事實不一定是這樣。例如,如果一個線程剛剛執行完? if(_state==5)語句,它就被其他線程搶占,調度器運行另一個線程。第二個線程現在進入 if 體,因為 state 的值仍是 5,所以將它遞增到 6。第一個線程現在再次被調度,在下一條語句中,State 遞增到 7。這時就發生了爭用條件,并顯示斷言消息:
public class StateObject {private int _state = 5;public void ChangeState(int loop) {if (_state == 5){_state++;If (_state != 6){Console.WriteLine($"Race?conditon?occurred?after?{loop}?loops");?Trace.Fail("race?condition");}}_state = 5;} }下面通過給任務定義一個方法來驗證這一點。SampleTask 類的 RaceCondition()方法將一個 StateObject 類作參數。在一個無限while循環中,調用ChangeState() 方法。變量 i 僅用于顯示斷言消息中的循環次數。
public class SampleTask {public void RaceCondition(object o){Trace.Assert(o?is?StateObject, "o?must?be?of?type?StateObject");?StateObject?state = o as StateObject;int?i?=?0;?while?(true){state.ChangeState(i++);}} }在程序的 Main() 方法中,新建了一個 StateObject 對象,它由所有任務共享。通過使用傳遞給 Task 的 Run 方法的 lambda 表達式調用 RaceCondition 方法來創建 Task 對象。然后,主線程等待用戶輸入。但是,因為可能出現爭用,所以程序很有可能在讀取用戶輸入前就掛起:
public void RaceConditions() {var state = new StateObject(); for?(int?i?=?0;?i?<?2;?i++){Task.Run(() => new SampleTask().RaceCondition(state));} }啟動程序,就會出現爭用條件。多久以后出現第一個爭用條件要取決于系統以及將程序構建為發布版本還是調試版本。如果構建為發布版本,該問題的出現次數就會比較多,因為代碼被優化了。如果系統中有多個 CPU 或使用雙核/四核 CPU,其中多個線程可以同時運行,則該問題也會比單核 CPU 的出現次數多。在單核CPU中,因為線程調度是搶占式的,也會出現該問題,只是沒有那么頻繁。
在我的系統上運行程序時,顯示在 85232 個循環后出現錯誤;在另一次運行程序時,顯示在 70037 個循環后出現錯誤。多次啟動應用程序,總是會得到不同的結果。
要避免該問題,可以鎖定共享的對象。這可以在線程中完成:用下面的 lock 語句鎖定在線程中共享的 state 變量。只有一個線程能在鎖定塊中處理共享的 state 對象。由于這個對象在所有的線程之間共享,因此,如果一個線程鎖定了 state,另一個線程就必須等待該鎖定的解除。一旦接受鎖定,線程就擁有該鎖定,直到該鎖定塊的末尾才解除鎖定。如果改變 state 變量引用的對象的每個線程都使用一個鎖定,就不會出現爭用條件。
public class SampleTask {public void RaceCondition(object o){Trace.Assert(o is StateObject, "o must be of type StateObject"); StateObject state = o as StateObject; int t = 0;while?(true){lock (state) // no race condition with this lock{state.ChangeState(i++);}}} }注意:
在下載的示例代碼中,需要取消鎖定語句的注釋,才能解決爭用條件的問題。
在使用共享對象時,除了進行鎖定之外,還可以將共享對象設置為線程安全的對象。在下面的代碼中, ChangeState() 方法包含一條 lock 語句。由于不能鎖定 state 變量本身(只有引用類型才能用于鎖定),因此定義一個object 類型的變量 sync,將它用于lock 語句。如果每次 state 的值更改時,都使用同一個同步對象來鎖定,就不會出現爭用條件。
public class StateObject {private int state = 5;private _object sync = new object(); public void ChangeState(int loop) {lock (_sync){if (_state == 5){_state++;if (_state != 6){Console.WriteLine($"Race condition occured after {loop} loops");Trace.Fail($"race?condition?at?{loop}");}}_state = 5;}}}?微信公眾號?
Dotnet講堂
總結
以上是生活随笔為你收集整理的C# 线程问题之争用条件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Xamarin效果第十三篇之弹窗Popu
- 下一篇: 特斯拉为何使用.NET 技术栈?