| 方法 | 用途 |
Enter(obj) / TryEnter(obj) | 取得物件鎖 (同 lock) |
Exit(obj) | 釋放物件鎖 |
Wait(obj) | **釋放鎖並封鎖目前執行緒**,進入物件的**等待隊列**,直到被 Pulse/PulseAll 喚醒並重新取得鎖 |
Pulse(obj) | 喚醒等待隊列中**第一個**執行緒,移至**就緒隊列** |
PulseAll(obj) | 喚醒等待隊列中**所有**執行緒 |
物件內部三個隊列
- 擁有者:目前持有鎖的執行緒參考。
- 就緒隊列:已準備好取得鎖的執行緒 (被 Pulse 喚醒者)。
- 等待隊列:呼叫
Wait 釋放鎖並等待通知的執行緒。
流程:執行緒 Wait → 釋放鎖 + 進入等待隊列 → 他執行緒 Pulse → 移至就緒隊列 → 鎖釋放 → 就緒隊列執行緒取得鎖繼續執行。
完整範例:生產者-消費者模式 (Monitor.Wait/Pulse)
場景:一個緩衝區 (Cell),生產者寫入整數,消費者讀取並顯示。**嚴格交替**:生產一個 → 消費一個 → 生產下一個...
using System;
using System.Threading;
namespace ProducerConsumerDemo
{
///
/// 共享緩衝區:單一整數槽位,支援同步讀寫
///
class Cell
{
private int _cellContents; // 緩衝資料
private bool _readerFlag = false; // false = 可寫入/正在寫入, true = 可讀取/已寫入
///
/// 消費者呼叫:讀取資料 (若無資料則等待)
///
public int ReadFromCell()
{
lock (this) // 取得 Cell 物件鎖
{
// 若目前不可讀取 (無新資料),釋放鎖並等待 Pulse
if (!_readerFlag)
{
try
{
Monitor.Wait(this); // 釋放鎖,進入等待隊列
}
catch (SynchronizationLockException ex)
{
Console.WriteLine($"[Read] 同步鎖例外: {ex.Message}");
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine($"[Read] 執行緒中斷: {ex.Message}");
}
}
// 被喚醒並重新取得鎖後,讀取資料
Console.WriteLine($"消費: {_cellContents}");
_readerFlag = false; // 重置旗標:資料已被消費
Monitor.Pulse(this); // 通知生產者:可寫入下一筆
}
return _cellContents;
}
///
/// 生產者呼叫:寫入資料 (若資料未被消費則等待)
///
public void WriteToCell(int n)
{
lock (this)
{
// 若目前不可寫入 (資料未被消費),釋放鎖並等待 Pulse
if (_readerFlag)
{
try
{
Monitor.Wait(this);
}
catch (SynchronizationLockException ex)
{
Console.WriteLine($"[Write] 同步鎖例外: {ex.Message}");
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine($"[Write] 執行緒中斷: {ex.Message}");
}
}
// 被喚醒並重新取得鎖後,寫入資料
_cellContents = n;
Console.WriteLine($"生產: {_cellContents}");
_readerFlag = true; // 設定旗標:有新資料可讀
Monitor.Pulse(this); // 通知消費者:有新資料
}
}
}
///
/// 生產者:連續寫入 1..quantity
///
class Producer
{
private readonly Cell _cell;
private readonly int _quantity;
public Producer(Cell cell, int quantity)
{
_cell = cell;
_quantity = quantity;
}
public void Run()
{
for (int i = 1; i <= _quantity; i++)
_cell.WriteToCell(i);
}
}
///
/// 消費者:連續讀取 quantity 次
///
class Consumer
{
private readonly Cell _cell;
private readonly int _quantity;
public Consumer(Cell cell, int quantity)
{
_cell = cell;
_quantity = quantity;
}
public void Run()
{
for (int i = 1; i <= _quantity; i++)
_cell.ReadFromCell();
}
}
class Program
{
static void Main()
{
Console.WriteLine("=== 生產者-消費者模式 (Monitor.Wait/Pulse) ===");
var cell = new Cell();
var producer = new Producer(cell, 20);
var consumer = new Consumer(cell, 20);
// 建立兩個執行緒
Thread producerThread = new Thread(producer.Run) { Name = "Producer" };
Thread consumerThread = new Thread(consumer.Run) { Name = "Consumer" };
try
{
producerThread.Start();
consumerThread.Start();
// 等待兩個執行緒結束
producerThread.Join();
consumerThread.Join();
}
catch (ThreadStateException ex)
{
Console.WriteLine($"執行緒狀態錯誤: {ex.Message}");
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine($"執行緒被中斷: {ex.Message}");
}
Console.WriteLine("程式結束。");
Console.ReadKey();
}
}
}
程式執行輸出
=== 生產者-消費者模式 (Monitor.Wait/Pulse) ===
生產: 1
消費: 1
生產: 2
消費: 2
生產: 3
消費: 3
...
生產: 20
消費: 20
程式結束。
流程詳解
- 生產者執行緒啟動,呼叫
WriteToCell(1) → 取得鎖 → _readerFlag=false 不等待 → 寫入 1 → _readerFlag=true → Pulse 喚醒消費者 → 釋放鎖。
- 消費者執行緒 (可能已在
ReadFromCell 中 Wait 等待) 被喚醒 → 重新取得鎖 → 讀取 1 → _readerFlag=false → Pulse 喚醒生產者 → 釋放鎖。
- 雙方以
_readerFlag 狀態旗標配合 Monitor.Wait/Pulse 實現**嚴格交替**,無忙碌等待、無資料遺漏、無競爭條件。
進階主題與現代替代方案
1. 緩衝區擴充:BlockingCollection<T> (推薦 .NET 4.0+)
System.Collections.Concurrent.BlockingCollection<T> 內建**有界緩衝區**、**生產者阻塞**、**消費者阻塞**、**完成標記**,完全取代手寫 Monitor 邏輯:
var buffer = new BlockingCollection(boundedCapacity: 10);
// 生產者
Task.Run(() => {
for (int i = 1; i <= 20; i++) buffer.Add(i); // 滿時自動阻塞
buffer.CompleteAdding(); // 標記完成
});
// 消費者
Task.Run(() => {
foreach (var item in buffer.GetConsumingEnumerable()) // 空時自動阻塞,CompleteAdding 後結束
Console.WriteLine($"消費: {item}");
});
2. 信號量 SemaphoreSlim:控制並存取數量
若允許 **N 個執行緒同時存取** 資源 (如連線池、並發限制),使用 SemaphoreSlim:
var sem = new SemaphoreSlim(3); // 最多 3 個並行
await sem.WaitAsync();
try { /* 存取資源 */ }
finally { sem.Release(); }
3. 讀寫鎖 ReaderWriterLockSlim:多讀單寫
資源「讀多寫少」時,ReaderWriterLockSlim 允許**多個讀取者並行**,寫入時**獨佔**:
var rwLock = new ReaderWriterLockSlim();
// 讀取
rwLock.EnterReadLock(); try { /* 讀取 */ } finally { rwLock.ExitReadLock(); }
// 寫入
rwLock.EnterWriteLock(); try { /* 寫入 */ } finally { rwLock.ExitWriteLock(); }
4. 非同步協調:TaskCompletionSource / Channel<T>
現代 .NET (Core/.NET 5+) 推薦使用 System.Threading.Channels.Channel<T> 實作高效能生產者-消費者:
var channel = Channel.CreateBounded(new BoundedChannelOptions(100)
{
FullMode = BoundedChannelFullMode.Wait
});
// 生產者
await channel.Writer.WriteAsync(item);
// 消費者
await foreach (var item in channel.Reader.ReadAllAsync())
{ /* 處理 */ }
總結
lock 是保護關鍵區段最簡潔、安全的語法糖;Monitor 提供 Wait/Pulse/PulseAll 實現執行緒間**精細協調與通訊**,是實作生產者-消費者、讀寫鎖、條件變數等進階同步模式的基石。理解 Monitor.Wait **釋放鎖並等待**、Pulse **喚醒單一等待者** 的機制,是掌握多執行緒同步的關鍵。實務開發中,優先使用 BlockingCollection、Channel<T>、SemaphoreSlim、ReaderWriterLockSlim 等高階並發集合與同步原語,減少手寫 Monitor 邏輯帶來的錯誤風險。核心原則:**鎖定範圍最小化、專用鎖物件、避免巢狀死結、善用現代高階抽象**。
- 上一篇文章: C# 多執行緒自動管理:ThreadPool 執行緒池完整教學與範例
- 下一篇文章: C# 多執行緒基礎操作:Thread 類別建立、啟動、終止、Join、狀態與優先級完整指南
Copyright ©
程式碼之家 All Rights Reserved