線程不安全與線程安全 示例代碼:
class Program { static void Main(string[] args) { Console.WriteLine("Incorrect counter"); var c = new Counter(); var t1 = new Thread(() => TestCounter(c)); var t2 = new Thread(() => TestCounter(c)); var t3 = new Thread(() => TestCounter(c)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine("Total count: {0}",c.Count); Console.WriteLine("--------------------------"); Console.WriteLine("Correct counter"); var c1 = new CounterWithLock(); t1 = new Thread(() => TestCounter(c1)); t2 = new Thread(() => TestCounter(c1)); t3 = new Thread(() => TestCounter(c1)); t1.Start(); t2.Start(); t3.Start(); t1.Join(); t2.Join(); t3.Join(); Console.WriteLine("Total count: {0}", c1.Count); Console.ReadKey(); } static void TestCounter(CounterBase c) { for (int i = 0; i < 100000; i++) { c.Increment(); c.Decrement(); } } class Counter : CounterBase { public int Count { get; private set; } public override void Increment() { Count++; } public override void Decrement() { Count--; } } class CounterWithLock : CounterBase { private readonly object _syncRoot = new Object(); public int Count { get; private set; } public override void Increment() { lock (_syncRoot) { Count++; } } public override void Decrement() { lock (_syncRoot) { Count--; } } } abstract class CounterBase { public abstract void Increment(); public abstract void Decrement(); } }
線程不安全與線程安全 執行結果:
線程不安全與線程安全 工作原理:
當主程序啟動時,創建了一個Counter類的對象。該類定義了一個可以遞增和遞減的簡單的計數器。然后我們啟動了三個線程。這三個線程共享同一個counter實例,在一個周期中進行一次遞增和一次遞減。這將導致不確定的結果。如果運行程序多次,則會打印出多個不同的計數器值。結果可能是0,但大多數情況下則不是0。
這是因為Counter類並不是線程安全的。當多個線程同時訪問counter對象時,第一個線程得到的counter值10並增加為11。然后第二個線程得到的值是11並增加為12。第一個線程得到counter值12,但是遞減操作發生前,第二個線程得到的counter值也是12。然后第一個線程將12遞減為11並保存回counter中,同時第二個線程進行了同樣的操作。結果我們進行了兩次遞增操作但是只有一次遞減操作,這顯然不對。這種情形被稱為競爭條件(race condition)。競爭條件是多線程環境中非常常見的導致錯誤的原因。
為了確保不會發生以上情形,必須保證當有線程操作counter對象時,所有其他線程必須等待直到當前線程完成操作。我們可以使用lock關鍵字來實現這種行為。
如果鎖定了一個對象,需要訪問該對象的所有其他線程則會處於阻塞狀態,並等待直到該對象解除鎖定。
注意:這里的線程安全代碼可能會導致嚴重的性能問題。