在多執行緒程式設計中,當多個執行緒需要存取共享資源(如檔案、記憶體變數、資料庫連線)時,若缺乏同步機制,極易發生**競爭條件**,導致資料損毀或邏輯錯誤。C# 提供了多種同步原語,其中 Mutex (互斥體) 是一種**跨行程**的重量級同步機制,適合用於**同一台電腦上多個應用程式實例**之間的協調,或是需要**跨 AppDomain** 同步的場景。本文將深入解析 System.Threading.Mutex 的運作原理、建立方式、核心方法 (WaitOne、WaitAll、WaitAny、ReleaseMutex) 以及搭配 AutoResetEvent 進行執行緒完成通知的完整範例。
| 方法 | 說明 | 回傳值 |
WaitOne() | 阻塞目前執行緒,直到取得 Mutex 擁有權 | bool (若設定逾時則回傳是否成功取得) |
WaitAll(Mutex[]) | 阻塞直到**陣列中所有** Mutex 都被釋放並取得擁有權 | bool |
WaitAny(Mutex[]) | 阻塞直到**陣列中任一** Mutex 被釋放並取得擁有權 | int (取得之 Mutex 索引) |
ReleaseMutex() | 釋放 Mutex 擁有權,喚醒下一個等待執行緒 | void (若目前執行緒非擁有者會拋出 ApplicationException) |
完整範例:四個執行緒搭配兩個 Mutex 與 AutoResetEvent
此範例示範主執行緒建立兩個 Mutex (gM1, gM2) 並初始擁有,啟動四個子執行緒分別等待不同條件:
t1:WaitAll(gM1, gM2) — 需兩個都釋放才能執行
t2:gM1.WaitOne() — 等 gM1 釋放
t3:WaitAny(gM1, gM2) — 等任一釋放
t4:gM2.WaitOne() — 等 gM2 釋放
主執行緒依序釋放 gM1、gM2,觀察執行順序。
using System;
using System.Threading;
namespace MutexDemo
{
class MutexSample
{
// 兩個具名 Mutex (跨行程可見),初始由主執行緒擁有
static Mutex gM1 = new Mutex(true, "Global\\Demo.Mutex.GM1");
static Mutex gM2 = new Mutex(true, "Global\\Demo.Mutex.GM2");
// 四個 AutoResetEvent 做為執行緒完成信號 (號誌燈)
static AutoResetEvent Event1 = new AutoResetEvent(false);
static AutoResetEvent Event2 = new AutoResetEvent(false);
static AutoResetEvent Event3 = new AutoResetEvent(false);
static AutoResetEvent Event4 = new AutoResetEvent(false);
const int ITERS = 100; // 保留欄位,本範例未使用
public static void Main()
{
Console.WriteLine("=== Mutex 同步化示範 ===");
Console.WriteLine("主執行緒初始擁有 gM1 和 gM2");
// 將四個 Event 放入陣列,供 WaitHandle.WaitAll 等待所有執行緒結束
WaitHandle[] events = { Event1, Event2, Event3, Event4 };
var sample = new MutexSample();
Thread t1 = new Thread(sample.T1_WaitAll);
Thread t2 = new Thread(sample.T2_WaitOne_GM1);
Thread t3 = new Thread(sample.T3_WaitAny);
Thread t4 = new Thread(sample.T4_WaitOne_GM2);
t1.Start();
t2.Start();
t3.Start();
t4.Start();
Thread.Sleep(2000); // 讓子執行緒先進入等待狀態
Console.WriteLine("主執行緒釋放 gM1");
gM1.ReleaseMutex(); // t2、t3 條件滿足
Thread.Sleep(1000);
Console.WriteLine("主執行緒釋放 gM2");
gM2.ReleaseMutex(); // t1、t4 條件滿足
// 等待四個執行緒全部結束 (對應四個 Event.Set())
WaitHandle.WaitAll(events);
Console.WriteLine("所有執行緒已結束,主執行緒繼續。");
Console.ReadKey();
}
// t1:等待 gM1 和 gM2 都被釋放
public void T1_WaitAll()
{
Console.WriteLine("[t1] 開始等待 WaitAll(gM1, gM2)");
Mutex[] mutexes = { gM1, gM2 };
Mutex.WaitAll(mutexes); // 封鎖直到兩個都取得
Thread.Sleep(2000); // 模擬工作
Console.WriteLine("[t1] 完成,WaitAll 條件滿足");
Event1.Set(); // 通知主執行緒 t1 結束
}
// t2:等待 gM1 釋放
public void T2_WaitOne_GM1()
{
Console.WriteLine("[t2] 開始等待 gM1.WaitOne()");
gM1.WaitOne();
Console.WriteLine("[t2] 完成,gM1 已取得");
Event2.Set();
}
// t3:等待 gM1 或 gM2 任一釋放
public void T3_WaitAny()
{
Console.WriteLine("[t3] 開始等待 WaitAny(gM1, gM2)");
Mutex[] mutexes = { gM1, gM2 };
int index = Mutex.WaitAny(mutexes); // 取得任一即返回索引
Console.WriteLine($"[t3] 完成,取得 Mutex 索引 {index} (0=gM1, 1=gM2)");
Event3.Set();
}
// t4:等待 gM2 釋放
public void T4_WaitOne_GM2()
{
Console.WriteLine("[t4] 開始等待 gM2.WaitOne()");
gM2.WaitOne();
Console.WriteLine("[t4] 完成,gM2 已取得");
Event4.Set();
}
}
}
程式執行輸出與流程分析
=== Mutex 同步化示範 ===
主執行緒初始擁有 gM1 和 gM2
[t1] 開始等待 WaitAll(gM1, gM2)
[t2] 開始等待 gM1.WaitOne()
[t3] 開始等待 WaitAny(gM1, gM2)
[t4] 開始等待 gM2.WaitOne()
主執行緒釋放 gM1
[t2] 完成,gM1 已取得
[t3] 完成,取得 Mutex 索引 0 (0=gM1, 1=gM2)
主執行緒釋放 gM2
[t1] 完成,WaitAll 條件滿足
[t4] 完成,gM2 已取得
所有執行緒已結束,主執行緒繼續。
流程解析
- 四個子執行緒啟動後,全部進入封鎖等待狀態。
- 主執行緒
ReleaseMutex(gM1) → t2 (WaitOne gM1) 與 t3 (WaitAny 任一) 條件滿足,t3 因 gM1 索引 0 先被釋放,故取得索引 0。
- 主執行緒
ReleaseMutex(gM2) → t1 (WaitAll 兩者皆需) 與 t4 (WaitOne gM2) 條件滿足。
- 主執行緒
WaitHandle.WaitAll(events) 等待四個 AutoResetEvent 全部變號 (Signaled),確保所有子執行緒確實結束。
AutoResetEvent 補充說明
- 類似**號誌燈**:
Set() = 綠燈 (有號誌),Reset() = 紅燈 (無號誌)。
WaitOne():執行緒在紅燈處等待,綠燈亮起時**自動重設為紅燈**並通過 (僅放行一個執行緒)。
- 適合**一對一**的執行緒完成通知。若需一對多通知,請使用
ManualResetEvent 或 CountdownEvent。
最佳實踐與常見陷阱
| 主題 | 建議 |
| 釋放 Mutex | 務必在 finally 區塊呼叫 ReleaseMutex(),防止例外導致永不釋放 |
| 避免死結 | 多 Mutex 時,所有執行緒**以相同順序**取得 (如先 gM1 再 gM2) |
| 跨行程同步 | 使用 Global\\ 前綴具名 Mutex,並處理 WaitHandle.WaitOne 逾時邏輯 |
| 單一實例應用程式 | 啟動時 Mutex.TryOpenExisting 或 new Mutex(true, name, out createdNew) 判斷 createdNew |
| 效能考量 | 單一行程內優先用 lock / Monitor / SemaphoreSlim,Mutex 為核心物件,開銷較大 |
總結
Mutex 是 C# 中功能最強大、唯一支援**跨行程同步**的互斥機制。透過 WaitOne/WaitAll/WaitAny 靈活組合等待條件,搭配 AutoResetEvent 或 ManualResetEvent 進行執行緒完成同步,能解決複雜的多執行緒協調問題。實務上,單一行程內請優先考慮 lock、SemaphoreSlim、ReaderWriterLockSlim 等輕量級機制;僅在需跨行程、跨 AppDomain 或單一實例應用程式時才使用具名 Mutex。記住:**永遠在 finally 區塊釋放 Mutex**,這是避免死結的鐵律。
- 上一篇文章: C# 資料庫存取元件:支援多資料庫提供者的通用 DataAccess 類別完整實作
- 下一篇文章: C# 多執行緒自動管理:Timer 定時器完整教學與範例
Copyright ©
程式碼之家 All Rights Reserved