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

C# 多執行緒原子操作:Interlocked 類別完整指南與效能實測

     編輯:關於C#

為什麼 i++ 不是執行緒安全的?

i++ 在 IL 層面分解為三步驟:

ldloc.0      // 載入變數值
ldc.i4.1     // 載入常數 1
add          // 相加
stloc.0      // 存回變數

多執行緒並發執行時,可能發生「讀取-修改-寫入」競爭:執行緒 A 讀到 5,執行緒 B 也讀到 5,兩者各自加 1 寫回 6,預期應為 7。

Interlocked 核心方法速查

方法功能回傳值適用型別
Increment(ref int)原子遞增 1遞增後的值int, long
Decrement(ref int)原子遞減 1遞減後的值int, long
Add(ref int, int)原子加法相加後的值int, long
Exchange(ref T, T)原子交換並取得舊值舊值所有參考型別、int、long、float、double、IntPtr
CompareExchange(ref T, T, T)若等於比較值則交換(CAS)原始值同上
Read(ref long)原子讀取 64 位元整數讀取值long

實測程式碼:驗證執行緒安全性

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

public class AtomicDemo
{
    private const int ThreadCount = 10;
    private const int Iterations = 100_000;

    public static void Run()
    {
        // 1. 非原子版
        int unsafeCounter = 0;
        Parallel.For(0, ThreadCount, _ =>
        {
            for (int i = 0; i < Iterations; i++) unsafeCounter++;
        });
        Console.WriteLine($"非原子: {unsafeCounter:N0} (預期 {ThreadCount * Iterations:N0})");

        // 2. Interlocked 版
        int safeCounter = 0;
        Parallel.For(0, ThreadCount, _ =>
        {
            for (int i = 0; i < Iterations; i++) Interlocked.Increment(ref safeCounter);
        });
        Console.WriteLine($"Interlocked: {safeCounter:N0}");

        // 3. lock 版(對照組)
        int lockCounter = 0;
        object sync = new();
        Parallel.For(0, ThreadCount, _ =>
        {
            for (int i = 0; i < Iterations; i++)
            {
                lock (sync) lockCounter++;
            }
        });
        Console.WriteLine($"lock: {lockCounter:N0}");

        // 4. 效能比較
        Benchmark();
    }

    private static void Benchmark()
    {
        const int N = 10_000_000;
        var sw = Stopwatch.StartNew();
        int c = 0;
        for (int i = 0; i < N; i++) Interlocked.Increment(ref c);
        sw.Stop();
        Console.WriteLine($"Interlocked {N:N0} 次: {sw.ElapsedMilliseconds} ms");

        sw.Restart();
        c = 0;
        object o = new();
        for (int i = 0; i < N; i++) lock (o) c++;
        sw.Stop();
        Console.WriteLine($"lock {N:N0} 次: {sw.ElapsedMilliseconds} ms");
    }
}

典型輸出與分析

非原子: 684,321 (預期 1,000,000)  // 嚴重遺失
Interlocked: 1,000,000             // 正確
lock: 1,000,000                    // 正確
Interlocked 10,000,000 次: 42 ms   // 約 2.4x 比 lock 快
lock 10,000,000 次: 101 ms

結論:Interlocked 以 CPU 指令級鎖定(x86 LOCK CMPXCHG、ARM LDADD)實現原子性,無核心模式切換開銷,效能遠優於 lock/Monitor。

進階用法:無鎖資料結構雛形

原子旗標(自旋鎖基礎)

private int _lockFlag = 0;

public void EnterSpinLock()
{
    while (Interlocked.CompareExchange(ref _lockFlag, 1, 0) != 0)
    {
        Thread.SpinWait(10); // 避免佔滿 CPU
    }
}

public void ExitSpinLock() => Interlocked.Exchange(ref _lockFlag, 0);

單次初始化模式

private object _lazyInstance;
private int _initFlag = 0;

public T GetInstance() where T : new()
{
    if (Interlocked.CompareExchange(ref _initFlag, 1, 0) == 0)
    {
        _lazyInstance = new T();
        Thread.MemoryBarrier(); // 確保寫入對其他執行緒可見
    }
    return (T)_lazyInstance;
}

64 位元計數器在 32 位元程序

private long _counter64;

public long Increment64() => Interlocked.Increment(ref _counter64);
// 32 位元程序下 long 非原子讀寫,Interlocked.Read/Increment 必須使用

常見誤區

誤區正確觀念
以為 volatile 就能保證 i++ 安全volatile 僅保證可見性與防止重排,不保證原子性
用 Interlocked.Add(ref x, 1) 替代 Increment功能相同但 Increment 語意更清晰、JIT 優化更好
對 double/decimal 使用 Interlocked不支援,需自行用 CompareExchange + BitConverter 雙重檢查
以為 Interlocked 解決所有並發問題僅適用單一變數原子操作,複雜不變條件仍需 lock/無鎖演算法

總結

System.Threading.Interlocked 是 .NET 多執行緒開發的基石工具:提供硬體級原子操作、效能優於鎖、API 簡潔。掌握 Increment/CompareExchange/Exchange 三大核心,即可處理計數器、旗標、單次初始化、無鎖堆疊/佇列等高頻並發場景。記住:單一變數原子 → Interlocked;多變數/複雜邏輯 → lock 或無鎖演算法。

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