首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 線程同步基礎:從非原子操作到死鎖防範完全指南

     編輯:關於C#

為什麼需要同步?一個計數器的悲劇

啟動 30 個線程並發執行 n++ 10000 次,理論結果應為 300000,但實際運行往往遠小於此值。原因在於 n++ 並非原子操作,其 IL 指令序列為:

  • ldsfld:載入字段值
  • ldc.i4.1:載入常數 1
  • add:相加
  • stfld:存回字段

線程在執行這四條指令間被打斷,會導致數據覆蓋,產生「髒數據」。

lock 關鍵字:最簡單的同步手段

使用 lock 將非原子操作包裝為臨界區:

private Object lockN = new Object();

private void IncN()
{
    lock (lockN)
    {
        n++;
    }
}

public void Inc()
{
    for (int i = 0; i < max; i++)
    {
        IncN();
    }
}

也可直接鎖定整個循環,但會嚴重降低併發度,不推薦。

Interlocked 類:無鎖的原子操作

.NET 提供 System.Threading.Interlocked 類,通過 CPU 指令級原子性保證操作不可分割:

public void Inc()
{
    for (int i = 0; i < max; i++)
    {
        Interlocked.Increment(ref n);
    }
}

同樣適用於 Decrement、Exchange 等場景,性能優於 lock。

死鎖:雙向等待的死循環

當兩個線程各持一把鎖並請求對方持有的鎖時,發生死鎖:

lock (objA) { Thread.Sleep(1000); lock (objB) { } }
lock (objB) { Thread.Sleep(2000); lock (objA) { } }

線程 A 持有 objA 等待 objB,線程 B 持有 objB 等待 objA,雙方永久阻塞。

死鎖解決方案

1. 統一鎖獲取順序

所有線程按相同順序請求鎖(如先 objA 後 objB),從根源上消除循環等待條件。

2. 超時機制

使用 Monitor.TryEnter 設置超時,避免無限等待(詳見下一講)。

3. 強制中斷(最後手段)

threadA.Abort();
threadB.Abort();

慎用,可能導致數據不一致或資源洩漏。

總結

同步的核心是將非原子操作原子化。lock 適合複雜邏輯,Interlocked 適合簡單數值操作。死鎖預防優於死鎖恢復,統一鎖順序是最佳實踐。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved