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

.NET 線程同步經典場景深度解析:鎖、Mutex、AutoResetEvent、生產者消費者模式

     編輯:ASP.NET 基礎

核心原則:同步原語選擇決策樹

  1. 同一個 AppDomain 內 → 優先用 Managed 輕量鎖 (lock/Monitor/ReaderWriterLockSlim/SemaphoreSlim)。
  2. 跨進程 → 必須用 Kernel 原語包裝 (Mutex/Semaphore/EventWaitHandle)。
  3. 高併發讀多寫少 → ReaderWriterLockSlim。
  4. 非阻塞/高效能 → Interlocked/Concurrent 集合/Channels (Core 3.0+)。

場景 1:多線程爭用獨佔資源

錯誤鎖定對象示範

// ❌ 鎖定 this:外部也可能 lock(this) 造成死鎖
lock (this) { ... }

// ❌ 鎖定 Type:公開型別,全程序共享,極易衝突
lock (typeof(MyClass)) { ... }

// ❌ 鎖定字串:字串驻留導致不同代碼鎖定同一物件
lock ("myLock") { ... }

// ❌ 鎖定值型別:每次裝箱產生新物件,鎖完全失效
int x = 0; lock (x) { ... }

正確做法:私有唯讀物件

private readonly object _lockObj = new object();

public void AccessResource() {
    lock (_lockObj) { // Monitor.Enter/Exit 語法糖
        // 臨界區:存取檔案、序列埠、非執行緒安全物件
    }
}

跨進程:Mutex

// 命名 Mutex:作業系統層面全域唯一
using (var mutex = new Mutex(false, "Global\\MyApp_ResourceLock")) {
    if (mutex.WaitOne(TimeSpan.FromSeconds(10))) {
        try { AccessSharedResource(); }
        finally { mutex.ReleaseMutex(); }
    }
    else { throw new TimeoutException("取得鎖逾時"); }
}
  • Global\\ 前綴:跨 Terminal Session 可見 (服務/系統帳號需求)。
  • 必須 try/finally 確保釋放,否則永久阻塞。

場景 2:一對一生產者/消費者 (BlockingCollection 最佳實踐)

傳統 AutoResetEvent 實作

var queue = new Queue<int>();
var signal = new AutoResetEvent(false); // 自動重置:喚醒一個即歸零

// 生產者
Task.Run(() => {
    while (true) {
        var item = Produce();
        lock (queue) queue.Enqueue(item);
        signal.Set(); // 通知消費者
    }
});

// 消費者
Task.Run(() => {
    while (true) {
        signal.WaitOne(); // 阻塞等待
        int item;
        lock (queue) { if (queue.Count > 0) item = queue.Dequeue(); else continue; }
        Consume(item);
    }
});

現代最佳實踐:BlockingCollection<T>

var buffer = new BlockingCollection<int>(boundedCapacity: 100); // 內建阻塞、限流

// 生產者
Task.Run(() => {
    try {
        while (!buffer.IsAddingCompleted) buffer.Add(Produce()); // 滿時自動阻塞
    } finally { buffer.CompleteAdding(); }
});

// 消費者
Task.Run(() => {
    foreach (var item in buffer.GetConsumingEnumerable()) // 空時阻塞,CompleteAdding 時結束
        Consume(item);
});

優點:無鎖程式碼、內建背壓、可限容量、支援多生產/多消費、可取消 (CancellationToken)。

場景 3:多生產者/多消費者 + 取消支援

var cts = new CancellationTokenSource();
var buffer = new BlockingCollection<WorkItem>(100);

var producers = Enumerable.Range(0, 3).Select(i => Task.Run(() => {
    while (!cts.Token.IsCancellationRequested) {
        var item = Produce();
        if (!buffer.TryAdd(item, 1000, cts.Token)) break; // 逾時/取消
    }
}, cts.Token)).ToArray();

var consumers = Enumerable.Range(0, 5).Select(i => Task.Run(() => {
    foreach (var item in buffer.GetConsumingEnumerable(cts.Token))
        Process(item);
}, cts.Token)).ToArray();

// 關閉流程
cts.CancelAfter(TimeSpan.FromMinutes(5));
Task.WaitAll(producers);
buffer.CompleteAdding();
Task.WaitAll(consumers);

同步原語比較表

原語適用範圍核心特性典型場景
lock (Monitor)AppDomain 內互斥、可重入、語法簡潔保護共享欄位/集合
Mutex跨進程核心物件、命名全域唯一單一實例應用、跨進程資源
SemaphoreSlimAppDomain 內計數信號、非阻塞等待限流、連線池控制
AutoResetEventAppDomain 內單一喚醒、自動重置簡單生產消費 (舊代碼)
ManualResetEventSlimAppDomain 內廣播喚醒、手動重置階段性啟動信號
ReaderWriterLockSlimAppDomain 內讀共享/寫獨佔快取、設定讀多寫少
BlockingCollectionAppDomain 內生產消費模式封裝、背壓管道處理、工作佇列
InterlockedAppDomain 內原子操作 (CAS)、無鎖計數器、旗標、無鎖資料結構
Concurrent 集合AppDomain 內無鎖/細粒度鎖、高併發Dictionary/Queue/Stack/Bag 共享

避坑指南

  • 鎖內不呼叫外部代碼/虛擬方法:防止死鎖/重入/效能雪崩。
  • 鎖粒度越小越好:但避免過度細碎導致活鎖。
  • 非同步代碼勿用 lock:跨 await 釋放執行緒,改用 SemaphoreSlim.WaitAsync 或 AsyncLock (Stephen Cleary)。
  • 釋放 Mutex/Semaphore 必須在 finally:防止例外導致永久持有。
  • 生產消費優先用 BlockingCollection/Channels:手寫 Queue+Event 易出 Bug。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved