多線程威力巨大,但失控的線程數與數據競爭能瞬間摧毀系統。本文聚焦兩大核心:信號量控制併發線程上限 (防上下文切換風暴) 與 鎖/監視器防競爭條件與死鎖 (守護數據一致性),給出經典代碼模式與反模式對比。
原理:線程非越多越好。單核跑多個 CPU 密集型線程,上下文切換開銷導致總耗時 2.1~3.5 倍 於單線程。線程池雖限制創建速率,但無法限制「同時運行」數量。
最佳實踐:Semaphore 守門員模式
static Semaphore _gate = new Semaphore(0, Environment.ProcessorCount); // 計數器 = 核心數
static void Execute(object state)
{
_gate.WaitOne(); // 獲取許可,滿則阻塞
try { /* 核心業務邏輯 */ }
finally { _gate.Release(); } // 歸還許可
}
static void RunBatch()
{
for (int i = 0; i < 2000; i++)
ThreadPool.QueueUserWorkItem(Execute); // 大量入隊,Semaphore 自動限流
}
Semaphore(0, maxCount):初始 0 可用,最大 maxCount (核心數)。
int x = 5;
// 線程 A, B 並發執行 x = x + 10;
// 可能結果: 15 (丟失更新) 或 25 (正確), 取決於調度順序。
lock / VB SyncLock (本質 Monitor.Enter/Exit)
object _lockObj = new object();
int x = 5;
lock (_lockObj) // 等同 Monitor.Enter/Exit
{
x = x + 10; // 原子操作
}
private readonly object 字段。當鎖持有時間需根據運行時條件動態決定 (如檢查標誌位決定是否繼續持有),直接用 Monitor.Enter/Exit/TryEnter 比 lock 更靈活。
// 線程 A
lock (obj1) { lock (obj2) { ... } }
// 線程 B
lock (obj2) { lock (obj1) { ... } }
// 交叉持有 → 死鎖
預防鐵律:
Monitor.TryEnter(lockObj, timeout) 失敗則回退重試,打破循環等待。多線程核心心法:線程池負責「排隊」,Semaphore 負責「限流」,lock/Monitor 負責「互斥」,統一順序負責「防死鎖」。拒絕手動管理線程生命週期,擁抱結構化併發控制。