多執行緒同時存取共享可變狀態 (讀寫變數、集合、檔案等) 時,若無協調,會發生**競態條件**:結果取決於執行緒交錯時序,導致資料不一致、邏輯錯誤。
| 類別/關鍵字 | 類型 | 跨進程 | 可重入 | 語意 | 典型用途 |
|---|---|---|---|---|---|
lock / Monitor | 使用者模式 | 否 | 是 (同執行緒) | 互斥鎖 | 保護記憶體內共享資料的臨界區 |
Mutex | 核心模式 | 是 (命名) | 是 | 互斥鎖 | 跨進程同步、單一實例應用 |
AutoResetEvent | 核心模式 | 是 (命名) | — | 信號/事件 | 單一執行緒等待單一信號 (生產者-消費者) |
ManualResetEvent | 核心模式 | 是 (命名) | — | 信號/事件 | 多執行緒等待同一信號 (廣播通知) |
ReaderWriterLockSlim | 使用者模式 | 否 | 否 | 讀寫鎖 | 讀多寫少場景,允許並發讀、獨佔寫 |
SemaphoreSlim | 使用者模式 | 否 | — | 計數信號 | 限制並發存取數量 (連線池、限流) |
Interlocked | 硬體指令 | 否 | — | 原子操作 | 單一變數增減/交換/比較交換 |
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;
}
Monitor.TryEnter 逾時放棄。lock 中 await (用 SemaphoreSlim.WaitAsync)。
// 單一實例應用
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();
}
// 生產者-消費者: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();
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();
}
}
private readonly SemaphoreSlim _sem = new(10); // 最大並發 10
public async Task ExecuteLimitedAsync(Func> action) {
await _sem.WaitAsync();
try {
return await action();
} finally {
_sem.Release();
}
}
lock (最簡潔、效能最好、可重入)。Mutex / EventWaitHandle。AutoResetEvent。ManualResetEvent / ManualResetEventSlim (使用者模式、效能更好)。ReaderWriterLockSlim。SemaphoreSlim (支援 WaitAsync)。Interlocked (無鎖、最快)。生產者-消費者、管道處理優先考慮 Channel<T> (無界/有界、非同步、背壓)、TransformBlock 等高階抽象,避免手寫低階同步。
同步原語各有適用場景,**lock 是 90% 情況的正解**。理解核心模式 (互斥、信號、讀寫分離、計數限流) 與跨進程/非同步特性,配合「臨界區最小化、固定順序、避免 await in lock」工程紀律,即能寫出正確、高效、免死結的並發代碼。