啟動 30 個線程並發執行 n++ 10000 次,理論結果應為 300000,但實際運行往往遠小於此值。原因在於 n++ 並非原子操作,其 IL 指令序列為:
ldsfld:載入字段值ldc.i4.1:載入常數 1add:相加stfld:存回字段線程在執行這四條指令間被打斷,會導致數據覆蓋,產生「髒數據」。
使用 lock 將非原子操作包裝為臨界區:
private Object lockN = new Object();
private void IncN()
{
lock (lockN)
{
n++;
}
}
public void Inc()
{
for (int i = 0; i < max; i++)
{
IncN();
}
}
也可直接鎖定整個循環,但會嚴重降低併發度,不推薦。
.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,雙方永久阻塞。
所有線程按相同順序請求鎖(如先 objA 後 objB),從根源上消除循環等待條件。
使用 Monitor.TryEnter 設置超時,避免無限等待(詳見下一講)。
threadA.Abort();
threadB.Abort();
慎用,可能導致數據不一致或資源洩漏。
同步的核心是將非原子操作原子化。lock 適合複雜邏輯,Interlocked 適合簡單數值操作。死鎖預防優於死鎖恢復,統一鎖順序是最佳實踐。