首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 多執行緒同步化機制:Mutex 互斥物件完整教學與範例

     編輯:關於C#

前言

在多執行緒程式設計中,當多個執行緒需要存取共享資源(如檔案、記憶體變數、資料庫連線)時,若缺乏同步機制,極易發生**競爭條件**,導致資料損毀或邏輯錯誤。C# 提供了多種同步原語,其中 Mutex (互斥體) 是一種**跨行程**的重量級同步機制,適合用於**同一台電腦上多個應用程式實例**之間的協調,或是需要**跨 AppDomain** 同步的場景。本文將深入解析 System.Threading.Mutex 的運作原理、建立方式、核心方法 (WaitOne、WaitAll、WaitAny、ReleaseMutex) 以及搭配 AutoResetEvent 進行執行緒完成通知的完整範例。

Mutex 核心觀念:出租車比喻

想像一台**出租車 (Mutex)** 和多位**乘客 (執行緒)**:

  • 乘客必須**等車** (WaitOne / WaitAll / WaitAny)。
  • 第一個上車的乘客**獨佔**車輛,其他乘客只能在車外等待。
  • 乘客下車時需**歸還車鑰匙** (ReleaseMutex),下一位乘客才能上車。
  • 若乘客忘記歸還 (未呼叫 ReleaseMutex),車輛將永遠無法被他人使用 → **死結**。

兩種 Mutex 建立模式

模式建立方式適用場景特點
區域 (Local/Unnamed)new Mutex() 或 new Mutex(false)單一行程內多執行緒同步無名稱,無法跨行程存取,效能較好
具名 (Named)new Mutex(false, "MyMutexName")跨行程、跨 AppDomain 同步 (如單一實例應用程式)作業系統核心物件,可透過 Mutex.OpenExisting 開啟既有具名 Mutex

具名 Mutex 關鍵參數

// initiallyOwned: true = 建立者立即擁有;false = 建立者不擁有,需 WaitOne 取得
// name: 具名 Mutex 的名稱,建議加上應用程式唯一前綴避免衝突,如 "Global\\MyApp.Mutex"
var mutex = new Mutex(false, "Global\\MyApp.SingleInstance");
  • Global\\ 前綴:在 Terminal Server / 多使用者環境下,讓 Mutex 於**全域命名空間**可見,所有 Session 共用。
  • Local\\ 前綴 (預設):僅於目前 Session 可見。

核心方法總覽

完整範例:四個執行緒搭配兩個 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 已取得
所有執行緒已結束,主執行緒繼續。

流程解析

  1. 四個子執行緒啟動後,全部進入封鎖等待狀態。
  2. 主執行緒 ReleaseMutex(gM1) → t2 (WaitOne gM1) 與 t3 (WaitAny 任一) 條件滿足,t3 因 gM1 索引 0 先被釋放,故取得索引 0。
  3. 主執行緒 ReleaseMutex(gM2) → t1 (WaitAll 兩者皆需) 與 t4 (WaitOne gM2) 條件滿足。
  4. 主執行緒 WaitHandle.WaitAll(events) 等待四個 AutoResetEvent 全部變號 (Signaled),確保所有子執行緒確實結束。

AutoResetEvent 補充說明

  • 類似**號誌燈**:Set() = 綠燈 (有號誌),Reset() = 紅燈 (無號誌)。
  • WaitOne():執行緒在紅燈處等待,綠燈亮起時**自動重設為紅燈**並通過 (僅放行一個執行緒)。
  • 適合**一對一**的執行緒完成通知。若需一對多通知,請使用 ManualResetEvent 或 CountdownEvent。

最佳實踐與常見陷阱

方法說明回傳值
WaitOne()阻塞目前執行緒,直到取得 Mutex 擁有權bool (若設定逾時則回傳是否成功取得)
WaitAll(Mutex[])阻塞直到**陣列中所有** Mutex 都被釋放並取得擁有權bool
WaitAny(Mutex[])阻塞直到**陣列中任一** Mutex 被釋放並取得擁有權int (取得之 Mutex 索引)
ReleaseMutex()釋放 Mutex 擁有權,喚醒下一個等待執行緒void (若目前執行緒非擁有者會拋出 ApplicationException)
主題建議
釋放 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**,這是避免死結的鐵律。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved