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

C# 多執行緒同步機制全解析:lock、Monitor、Mutex、EventWaitHandle、ReaderWriterLockSlim

     編輯:C# 入門

同步問題本質

多執行緒同時存取共享可變狀態 (讀寫變數、集合、檔案等) 時,若無協調,會發生**競態條件**:結果取決於執行緒交錯時序,導致資料不一致、邏輯錯誤。

核心同步原語比較

類別/關鍵字類型跨進程可重入語意典型用途
lock / Monitor使用者模式否是 (同執行緒)互斥鎖保護記憶體內共享資料的臨界區
Mutex核心模式是 (命名)是互斥鎖跨進程同步、單一實例應用
AutoResetEvent核心模式是 (命名)—信號/事件單一執行緒等待單一信號 (生產者-消費者)
ManualResetEvent核心模式是 (命名)—信號/事件多執行緒等待同一信號 (廣播通知)
ReaderWriterLockSlim使用者模式否否讀寫鎖讀多寫少場景,允許並發讀、獨佔寫
SemaphoreSlim使用者模式否—計數信號限制並發存取數量 (連線池、限流)
Interlocked硬體指令否—原子操作單一變數增減/交換/比較交換

1. lock / Monitor:最常用的記憶體鎖


private readonly object _lockObj = new(); // 專用鎖物件,不可為 this/字串/值型別裝箱

public void Add(int n) {
    lock (_lockObj) { // 編譯為 Monitor.Enter/Exit + try/finally
        _count += n;
    }
}

// 等價手寫 (需確保 finally 釋放)
public void Delete(int n) {
    Monitor.Enter(_lockObj);
    try {
        _count -= n;
    } finally {
        Monitor.Exit(_lockObj);
    }
}

// 進階:TryEnter 帶逾時,避免死結
public bool TryAdd(int n, int timeoutMs = 1000) {
    if (Monitor.TryEnter(_lockObj, timeoutMs)) {
        try { _count += n; return true; }
        finally { Monitor.Exit(_lockObj); }
    }
    return false;
}
死結預防:
1. 固定鎖獲取順序 (如先鎖 A 再鎖 B)。
2. 使用 Monitor.TryEnter 逾時放棄。
3. 臨界區最小化、避免呼叫外部/虛擬方法。
4. 絕不在 lock 中 await (用 SemaphoreSlim.WaitAsync)。

2. Mutex:跨進程同步


// 單一實例應用
using var mutex = new Mutex(true, "Global\\MyAppSingleton", out bool createdNew);
if (!createdNew) {
    MessageBox.Show("應用程式已在執行中");
    return;
}
Application.Run(new MainForm());
// 進程結束自動釋放 Mutex

// 一般跨進程保護
var mutex = new Mutex(false, "Global\\SharedResourceLock");
mutex.WaitOne(); // 阻塞直到取得
try {
    // 存取共享記憶體/檔案/登錄
} finally {
    mutex.ReleaseMutex();
}

3. AutoResetEvent / ManualResetEvent:執行緒協調


// 生產者-消費者:AutoResetEvent (自動重置,喚醒單一等待者)
var _signal = new AutoResetEvent(false);
Task.Run(() => { // 消費者
    _signal.WaitOne(); // 等待信號
    ProcessItem();
});
// 生產者
_queue.Enqueue(item);
_signal.Set(); // 釋放一個等待者

// 廣播通知:ManualResetEvent (手動重置,喚醒所有等待者)
var _allDone = new ManualResetEvent(false);
// 多個工作者等待
_allDone.WaitOne();
// 完成時
_allDone.Set(); // 釋放所有
// 重置供下輪使用
_allDone.Reset();

4. ReaderWriterLockSlim:讀多寫少最佳化


private readonly ReaderWriterLockSlim _rwLock = new();
private readonly Dictionary _cache = new();

public string Get(int key) {
    _rwLock.EnterReadLock();
    try {
        return _cache.TryGetValue(key, out var v) ? v : null;
    } finally {
        _rwLock.ExitReadLock();
    }
}

public void Set(int key, string value) {
    _rwLock.EnterWriteLock();
    try {
        _cache[key] = value;
    } finally {
        _rwLock.ExitWriteLock();
    }
}

// 可升級讀鎖:讀取時檢查需寫入,原子升級為寫鎖 (避免競態)
public void AddOrUpdate(int key, string value) {
    _rwLock.EnterUpgradeableReadLock();
    try {
        if (!_cache.ContainsKey(key)) {
            _rwLock.EnterWriteLock();
            try { _cache[key] = value; }
            finally { _rwLock.ExitWriteLock(); }
        }
    } finally {
        _rwLock.ExitUpgradeableReadLock();
    }
}

5. SemaphoreSlim:非同步友好的限流器


private readonly SemaphoreSlim _sem = new(10); // 最大並發 10

public async Task ExecuteLimitedAsync(Func> action) {
    await _sem.WaitAsync();
    try {
        return await action();
    } finally {
        _sem.Release();
    }
}

選型指南

  1. **記憶體內共享資料、短臨界區** → lock (最簡潔、效能最好、可重入)。
  2. **需跨進程同步** → 命名 Mutex / EventWaitHandle。
  3. **單一生產者喚醒單一消費者** → AutoResetEvent。
  4. **單一事件喚醒多個等待者** → ManualResetEvent / ManualResetEventSlim (使用者模式、效能更好)。
  5. **讀多寫少、字典/快取** → ReaderWriterLockSlim。
  6. **限制並發數量、非同步等待** → SemaphoreSlim (支援 WaitAsync)。
  7. **單一整數/物件參考原子操作** → Interlocked (無鎖、最快)。

現代替代:System.Threading.Channels / TPL Dataflow

生產者-消費者、管道處理優先考慮 Channel<T> (無界/有界、非同步、背壓)、TransformBlock 等高階抽象,避免手寫低階同步。

結語

同步原語各有適用場景,**lock 是 90% 情況的正解**。理解核心模式 (互斥、信號、讀寫分離、計數限流) 與跨進程/非同步特性,配合「臨界區最小化、固定順序、避免 await in lock」工程紀律,即能寫出正確、高效、免死結的並發代碼。

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