博客 / 詳情

返回

你真的理解 Interlocked.Exchange 嗎?C#.NET 原子操作詳解

什麼是 Interlocked.Exchange?

Interlocked.ExchangeSystem.Threading.Interlocked 類中的靜態方法,用於原子地替換(交換)一個變量的值,並返回該變量的舊值。整個讀-改-寫過程是不可分割的,在多線程環境中保證線程安全。

  • 核心作用:無鎖地(lock-free)將變量設置為新值,同時獲取舊值。
  • 常見重載:
public static int Exchange(ref int location, int value);
public static long Exchange(ref long location, long value);
public static float Exchange(ref float location, float value);
public static double Exchange(ref double location, double value);
public static T Exchange<T>(ref T location, T value) where T : class;  // 引用類型
public static object? Exchange(ref object? location, object? newValue);
  • 參數:

    • location:要替換的共享變量(必須ref傳遞,直接操作內存地址)。
    • value / newValue:要寫入的新值。
  • 返回值:操作前的舊值。

底層基於 CPU 的原子交換指令(如 x86xchg),性能極高。

為什麼使用 Interlocked.Exchange?

普通的 “讀取值 + 賦值新值” 是兩步非原子操作,多線程下會因競態條件導致邏輯錯誤

  • 典型問題:
// 非原子操作:讀取舊值和賦值新值之間可能被其他線程打斷
private static int _flag = 0;
public static int UnsafeReplace(int newValue)
{
    int oldValue = _flag; // 步驟1:讀取舊值
    _flag = newValue;     // 步驟2:賦值新值
    return oldValue;      // 返回舊值
}

若兩個線程同時執行上述代碼,可能出現 “線程 A 讀取舊值後,線程 B 已修改值,線程 A 最終返回的舊值與實際賦值前的舊值不一致” 的問題。
Interlocked.Exchange 將 “讀取舊值 + 設置新值” 合併為不可中斷的原子操作,從底層杜絕競態條件,且無需阻塞線程(無鎖)。

  • Exchange 的優勢:

    • 原子性:讀取舊值和寫入新值一步完成。
    • 高性能:無鎖,開銷極低。
    • 可見性:所有線程立即可見。
    • 有序性:寫入前的操作不會被重排到之後。
    • 無鎖:不阻塞線程。
    • 適用場景:

      • 線程安全的狀態切換(如啓用/禁用標誌)。
      • 實現懶加載單例(替換 null 為實例)。
      • 原子更新引用(如緩存對象)。
      • 實現 SpinLock 或自定義同步原語。

核心原理:CPU 級原子指令

Interlocked.Exchange 的原子性依賴 CPU 硬件指令:

  • x86 架構:使用 LOCK XCHG 指令(LOCK 前綴獨佔內存總線,阻止其他 CPU 核心修改該內存地址);
  • ARM 架構:使用 SWP(交換指令)或 LDXR/STXR(加載 - 存儲獨佔)指令。

同時,該操作會觸發全內存屏障(Full Memory fence):

  • 保證操作前後的內存讀寫不會被 CPU 重排序;
  • 確保所有線程能立即看到變量的最新值(避免 CPU 緩存導致的 “髒讀”)。

基礎使用示例

最簡單的原子替換(返回原始值)

private static int _counter = 0;

public static void TestExchange()
{
    // 目標:將_counter從0替換為100,獲取原始值
    int newValue = 100;
    int originalValue = Interlocked.Exchange(ref _counter, newValue);
    
    Console.WriteLine($"原始值:{originalValue},新值:{_counter}");
    // 輸出:原始值:0,新值:100

    // 再次替換:將_counter從100替換為200
    originalValue = Interlocked.Exchange(ref _counter, 200);
    Console.WriteLine($"原始值:{originalValue},新值:{_counter}");
    // 輸出:原始值:100,新值:200
}

線程安全標誌位

private int _isProcessing = 0; // 0=未處理, 1=處理中

public bool StartProcessing()
{
    // 如果當前為0(未處理),則設為1(處理中)並返回true
    return Interlocked.Exchange(ref _isProcessing, 1) == 0;
}

public void EndProcessing()
{
    // 重置為未處理狀態
    Interlocked.Exchange(ref _isProcessing, 0);
}

一次性資源釋放(防止重複 Dispose)

private IDisposable? _resource;

public void Dispose()
{
    var res = Interlocked.Exchange(ref _resource, null);
    res?.Dispose();
}

無鎖發佈對象(Safe Publication)

private object? _instance;

public object GetOrCreate()
{
    if (_instance != null)
        return _instance;

    var newObj = new object();
    Interlocked.Exchange(ref _instance, newObj);
    return newObj;
}

多線程下的原子狀態切換

Exchange 實現線程安全的 “一次性初始化”(狀態從未初始化→已初始化,僅執行一次):

// 狀態枚舉:0=未初始化,1=初始化中,2=已初始化
private static int _initState = 0;
// 模擬初始化成本高的對象
private static ExpensiveObject? _expensiveObj;

/// <summary>
/// 線程安全的一次性初始化
/// </summary>
public static ExpensiveObject GetExpensiveObject()
{
    // 1. 原子替換:將狀態從0(未初始化)改為1(初始化中)
    int originalState = Interlocked.Exchange(ref _initState, 1);
    
    // 2. 只有原始狀態是0的線程,執行初始化
    if (originalState == 0)
    {
        Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}:執行初始化...");
        _expensiveObj = new ExpensiveObject(); // 耗時初始化
        // 3. 初始化完成,將狀態改為2(已初始化)
        Interlocked.Exchange(ref _initState, 2);
    }
    else
    {
        // 其他線程等待初始化完成
        while (_initState != 2)
        {
            Thread.SpinWait(10); // 自旋等待(輕量級)
        }
        Console.WriteLine($"線程{Thread.CurrentThread.ManagedThreadId}:初始化已完成,直接返回對象");
    }
    
    return _expensiveObj!;
}

// 測試:10個線程同時調用,僅1個線程執行初始化
var tasks = Enumerable.Range(0, 10)
    .Select(_ => Task.Run(() => GetExpensiveObject()))
    .ToList();
Task.WaitAll(tasks.ToArray());

// 模擬創建成本高的對象
public class ExpensiveObject
{
    public ExpensiveObject()
    {
        Thread.Sleep(1000); // 模擬1秒耗時初始化
    }
}

輸出結果

僅 1 個線程執行初始化,其餘線程等待後直接返回對象:

線程3:執行初始化...
線程4:初始化已完成,直接返回對象
線程5:初始化已完成,直接返回對象
...(其餘線程均輸出此內容)

高級應用場景

單例模式(結合 Exchange 實現懶加載)

Exchange 替代 lock 實現高性能單例

public sealed class LazySingleton
{
    // 單例實例(引用類型)
    private static LazySingleton? _instance;
    // 初始化標記:0=未初始化,1=已初始化
    private static int _initialized = 0;

    private LazySingleton() { } // 私有構造

    public static LazySingleton Instance
    {
        get
        {
            // 原子替換:將_initialized從0改為1,僅第一次調用返回0
            if (Interlocked.Exchange(ref _initialized, 1) == 0)
            {
                _instance = new LazySingleton(); // 僅執行一次初始化
            }
            return _instance!;
        }
    }
}

取消令牌的原子重置

Exchange 原子替換 CancellationTokenSource,實現 “取消後重置” 的線程安全邏輯:

private static CancellationTokenSource? _cts = new CancellationTokenSource();

/// <summary>
/// 原子重置取消令牌(取消舊令牌,創建新令牌)
/// </summary>
public static CancellationToken ResetCts()
{
    // 1. 原子替換舊的Cts為新實例,獲取舊實例
    CancellationTokenSource? oldCts = Interlocked.Exchange(ref _cts, new CancellationTokenSource());
    // 2. 取消舊令牌(避免舊任務繼續執行)
    if (oldCts != null)
    {
        oldCts.Cancel();
        oldCts.Dispose();
    }
    // 3. 返回新令牌
    return _cts.Token;
}

引用類型:原子替換對象引用(最常用)

private ExpensiveObject? _cache = null;

public ExpensiveObject GetOrCreate()
{
    // 如果為 null,原子替換為新實例
    var newInstance = new ExpensiveObject();
    var oldInstance = Interlocked.Exchange(ref _cache, newInstance);

    return oldInstance ?? newInstance;  // 如果舊值為 null,返回新實例
}

與 Interlocked.CompareExchange 的關係

方法 行為
Exchange 無條件替換
CompareExchange 條件替換(CAS)

對比示例

// Exchange:不管原來是什麼,都換
Interlocked.Exchange(ref state, 1);

// CompareExchange:只有 state == 0 才換
Interlocked.CompareExchange(ref state, 1, 0);

總結

Interlocked.Exchange 是 .NET 中最簡單、最快、最安全的“原子替換”操作,
它是實現一次性執行、無鎖狀態切換、安全對象發佈的基石。
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.