傳統多線程編程習慣:lock → 檢查條件 → 執行操作。這實質是在「某一絕對時間點」採樣狀態,存在固有競態:檢查通過到實際操作間,條件可能已失效。
不應輪詢條件,而應等待事件觸發。核心同步原語:Semaphore(信號量)充當「事件觸發器」。
public void Push(Item item)
{
lock(_lock)
{
while (queue.Count >= MaxSize) Monitor.Wait(_lock); // 條件等待
queue.Enqueue(item);
Monitor.PulseAll(_lock);
}
}
public Item Pop()
{
lock(_lock)
{
while (queue.Count == 0) Monitor.Wait(_lock); // 條件等待
var item = queue.Dequeue();
Monitor.PulseAll(_lock);
return item;
}
}
Semaphore _produce = new Semaphore(int.MaxValue, int.MaxValue); // 生產許可
Semaphore _consume = new Semaphore(0, int.MaxValue); // 消費許可
public void Push(Item item)
{
_produce.WaitOne(); // 等待生產事件(槽位可用)
lock(_lock) queue.Enqueue(item);
_consume.Release(); // 觸發消費事件(有產品)
}
public Item Pop()
{
_consume.WaitOne(); // 等待消費事件(有產品)
lock(_lock) var item = queue.Dequeue();
_produce.Release(); // 觸發生產事件(釋放槽位)
return item;
}
| 維度 | 條件判斷 + 鎖 | 事件驅動 + Semaphore |
|---|---|---|
| 代碼複雜度 | 高(while + wait + pulse) | 低(Wait/Release 直觀) |
| 虛假喚醒處理 | 需 while 循環防禦 | 語義級保證,無虛假喚醒 |
| 性能 | 鎖競爭 + 上下文切換 | 輕量級內核對象,無用戶態鎖 |
| 邏輯表達力 | 描述「如何等待」 | 描述「等待什麼事件」 |
簡單計數器場景可用 Interlocked.Increment/Decrement 實現無鎖協調,配合 SpinWait 適合極短關鍵區。
多線程編程的本質是協調並發事件的發生順序,而非在鎖保護下檢查條件。採用 Semaphore、EventWaitHandle、TaskCompletionSource 等事件驅動原語,能從根本上消除競態條件,編寫出更簡潔、正確、高效的並發代碼。這是從「共享內存通訊」到「通訊共享內存」的思維升級。