雖然多線程能顯著提升程式執行效率,但也引入了資料競爭與死鎖等複雜問題。本文透過實例程式碼,深入剖析非同步操作導致的「髒資料」現象,並從 IL 層面揭示 n++ 非原子性的本質。
以下程式建立 30 條線程,每條線程對共享變數 n 執行 10,000 次遞增操作。理論結果應為 300,000,但實際執行往往遠小於此值:
class Increment
{
private int n = 0;
private int max;
public Increment(int max) { this.max = max; }
public int Result { get { return n; } set { n = value; } }
public void Inc()
{
for (int i = 0; i < max; i++) { n++; }
}
}
class Program
{
public static void Main()
{
Increment inc = new Increment(10000);
Thread[] threads = new Thread[30];
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(inc.Inc);
threads[i].Start();
}
for (int i = 0; i < threads.Length; i++)
{
threads[i].Join();
}
Console.WriteLine(inc.Result);
}
}
從 C# 原始碼看,n++ 只是簡單的自增語句,但編譯為 IL 後卻包含四條指令:
ldsfld:將 n 的初始值壓入評估堆疊ldc.i4.1:將常數 1 壓入堆疊add:彈出堆疊頂端兩值相加,結果壓回堆疊stfld:彈出堆疊值,更新類別欄位 n雖然每條 IL 指令本身是線程安全的,但 n++ 需四步完成。若任一步驟在 stfld 前因時間片輪換被中斷,其他線程讀取到的仍是舊值,導致更新丟失,產生「髒資料」。
複合操作(讀取-修改-寫入)在多線程環境下必須採取同步機制(如 lock、Interlocked、Monitor),確保原子性,避免競態條件。