雖然在專案中頻繁使用多執行緒,但閱讀 Jeffrey Richter 關於鎖的文章後發現,自己缺乏真正的多執行緒思維。本文從自訂鎖 Optex 實作切入,探討並發程式設計的核心心法。
public class TestThreading {
private object lockThis = new object();
public void Function() {
lock (lockThis) {
// 存取執行緒敏感資源
}
}
}
lock 是語法糖,實際呼叫 Monitor.Enter/Exit。注意 Monitor.Pulse/PulseAll 若等待佇列為空會持續阻塞,可能造成類死鎖。
public sealed class Optex : IDisposable {
private int m_Waiters = 0;
private Semaphore m_WaiterLock = new Semaphore(0, Int32.MaxValue);
public void Enter() {
Thread.BeginCriticalRegion();
if (Interlocked.Increment(ref m_Waiters) == 1) return; // 首個執行緒直接取得
m_WaiterLock.WaitOne(); // 其餘等待
}
public void Exit() {
if (Interlocked.Decrement(ref m_Waiters) > 0)
m_WaiterLock.Release(1); // 喚醒一個等待者
Thread.EndCriticalRegion();
}
...
}
public sealed class Optex : IDisposable {
private int m_LockState = 0; // 位元組合:0=Free, 1=Owned, 2=WaiterCount
private const int c_lsFree = 0x0, c_lsOwned = 0x1, c_1Waiter = 0x2;
private Semaphore m_WaiterLock = new Semaphore(0, Int32.MaxValue);
public void Enter() {
Thread.BeginCriticalRegion();
while (true) {
int ls = InterlockedOr(ref m_LockState, c_lsOwned);
if ((ls & c_lsOwned) == c_lsFree) return; // 成功取得
if (IfThen(ref m_LockState, ls, ls + c_1Waiter)) {
m_WaiterLock.WaitOne();
}
}
}
public void Exit() {
int ls = InterlockedAnd(ref m_LockState, ~c_lsOwned);
if (ls != c_lsOwned) { // 有等待者
ls &= ~c_lsOwned;
if (IfThen(ref m_LockState, ls & ~c_lsOwned, ls - c_1Waiter))
m_WaiterLock.Release(1);
}
Thread.EndCriticalRegion();
}
// 原子化位元操作:CompareExchange 迴圈
private static int InterlockedOr(ref int target, int with) { ... }
private static int InterlockedAnd(ref int target, int with) { ... }
private static bool IfThen(ref int val, int @if, int then) { ... }
...
}
InterlockedOr/And 使用 CompareExchange 迴圈確保讀-修改-寫不可分割:
do {
i = j;
j = Interlocked.CompareExchange(ref target, i | with, i);
} while (i != j);
若目標值在操作期間被其他執行緒修改,迴圈重試直到成功。這是無鎖程式設計的核心模式。
單一 int 以位元編碼多重狀態:
Owned(是否被持有)WaiterCount(等待執行緒數)原子操作同時更新多個邏輯欄位,避免競爭條件。
傳統思維:
Enter: WaitOne()
Exit: Release(1)
優化思維:
關鍵差異:WaitOne/Release 前後皆驗證狀態未被並發修改,確保只在必要時進入核心模式。
Enter 的 while(true) 解決「搶佔式排程導致獲取後未實際持有」的極罕見情況:
這種「虛假喚醒」處理是健壯並發元件的標準做法。
多執行緒程式設計複雜但充滿挑戰性。隨著多核心普及,掌握無鎖技術、記憶體模型、鎖優化策略將成為核心競爭力。從 Optex 實作中提煉的思維模式,適用於所有並發控制場景。