多執行緒存取同一物件實例時,若無同步機制,會導致資料競爭:讀取到中間狀態、寫入覆蓋、不變量破壞。lock 僅能保護程式碼區塊,Monitor 提供物件級鎖 + 條件變數,支援更精細的協作模式。
Queue q = new Queue();
Monitor.Enter(q); // 取得鎖,其他執行緒阻塞
// 關鍵區:僅當前執行緒可操作 q
Monitor.Exit(q); // 釋放鎖
最佳實務:永遠在 try/finally 中釋放:
Monitor.Enter(obj);
try { /* critical section */ }
finally { Monitor.Exit(obj); }
| 隊列 | 內容 | 狀態轉換 |
|---|---|---|
| 擁有者 | 目前持有鎖的執行緒參考 | Enter → 擁有者; Exit → 清空 |
| 就緒隊列 | 已準備好取得鎖的執行緒 | Pulse → 從等待隊列移入; Exit → 從就緒隊列取一個成為擁有者 |
| 等待隊列 | 呼叫 Wait 等待條件的執行緒 | Wait → 釋放鎖並進入; Pulse → 移至就緒隊列 |
public class Cell
{
int _cellContents;
bool _readerFlag; // true = 已生產可讀, false = 可寫
public int ReadFromCell()
{
lock (this)
{
if (!_readerFlag)
{
try { Monitor.Wait(this); }
catch (SynchronizationLockException) { }
catch (ThreadInterruptedException) { }
}
Console.WriteLine($"Consume: {_cellContents}");
_readerFlag = false;
Monitor.Pulse(this);
}
return _cellContents;
}
public void WriteToCell(int n)
{
lock (this)
{
if (_readerFlag)
{
try { Monitor.Wait(this); }
catch (SynchronizationLockException) { }
catch (ThreadInterruptedException) { }
}
_cellContents = n;
Console.WriteLine($"Produce: {_cellContents}");
_readerFlag = true;
Monitor.Pulse(this);
}
}
}
public class CellProd
{
readonly Cell _cell;
readonly int _quantity;
public CellProd(Cell cell, int qty) { _cell = cell; _quantity = qty; }
public void ThreadRun()
{
for (int i = 1; i <= _quantity; i++)
_cell.WriteToCell(i);
}
}
public class CellCons
{
readonly Cell _cell;
readonly int _quantity;
public CellCons(Cell cell, int qty) { _cell = cell; _quantity = qty; }
public void ThreadRun()
{
for (int i = 1; i <= _quantity; i++)
_cell.ReadFromCell();
}
}
public class MonitorSample
{
public static void Main()
{
var cell = new Cell();
var prod = new CellProd(cell, 20);
var cons = new CellCons(cell, 20);
var producer = new Thread(prod.ThreadRun);
var consumer = new Thread(cons.ThreadRun);
producer.Start();
consumer.Start();
producer.Join();
consumer.Join();
Console.ReadLine();
}
}
Produce: 1
Consume: 1
Produce: 2
Consume: 2
...
Produce: 20
Consume: 20
_readerFlag 區分「可讀」與「可寫」狀態Monitor.Wait 原子性釋放鎖並進入等待隊列Monitor.Pulse 僅喚醒一個對方(因只有一個生產者/消費者)Wait/Pulse 在同一 lock 內,避免競態Enter,需對應 ExitSynchronizationLockException 表示在非同步區呼叫 Wait/PulseMonitor 是 .NET 執行緒同步的基石,lock 編譯為 Monitor.Enter/Exit。掌握 Wait/Pulse 條件變數模式,可解決生產者-消費者、讀寫鎖、資源池等經典協作問題。現代高階抽象:BlockingCollection<T>、SemaphoreSlim、Channels 內部皆建立於 Monitor 之上。