.NET 中使用 Mutex 進行跨越進程邊界的同步

轉載至:.NET 中使用 Mutex 進行跨越進程邊界的同步html

Mutex 是 Mutual Exclusion 的縮寫,是互斥鎖,用於防止兩個線程同時對計算機上的同一個資源進行訪問。不過相比於其餘互斥的方式,Mutex 可以跨越線程邊界。api


Mutex 是什麼?

與其餘線程同步的方式同樣,Mutex 也提供對資源的互斥訪問;不過 Mutex 使用的系統資源會比 Monitor 更多,而 Monitor 就是實現 C# 中 lock 關鍵字所用的鎖。app

用更多的系統資源,帶來更強大的功能 —— Mutex 能進行跨越應用程序域邊界的封送,能進行跨越進程邊界的線程同步。異步

簡單的 Mutex(不能跨進程互斥)

最簡單的 Mutex 的使用方法就是直接 new 出來,而後使用 Wait 進行等待,使用 ReleaseMutex 進行釋放async

 1 private readonly Mutex _mutex = new Mutex();
 2 
 3 private void UseResource()
 4 {
 5     _mutex.WaitOne();
 6     
 7     // 等待一小段時間,僞裝正在使用公共資源。這裏的一段代碼在單個進程以內將沒法重入。
 8     Thread.Sleep(500);
 9 
10     _mutex.ReleaseMutex();
11 }

參數中有一個 initiallyOwned 參數,若是指定爲 true 表示建立這個 Mutex 的線程擁有這個資源(不須要等待),當這個線程調用 ReleaseMutex 以後其餘線程的 WaitOne 纔會生效。函數

不過這種方式不能達到跨進程同步的效果,因此實際上本文並不會過多描述這種互斥方式。post

建立跨進程互斥的 Mutex

要建立跨進程互斥的 Mutex,必需要給 Mutex 指定名稱。ui

使用 new Mutex(false, "Walterlv.Mutex") 建立一個命名的互斥鎖,以便進行跨進程的資源互斥訪問。spa

在使用這個構造函數重載的時候,第一個參數 initiallyOwned 建議的取值爲 false。由於當你指定爲 true 時,說明你但願此線程是初始建立此 Mutex 的線程,然而因爲你是直接 new 出來的,因此你實質上是沒法得知你究竟是不是第一個 new 出來的。線程

 1 class Program
 2 {
 3     static async Task Main(string[] args)
 4     {
 5         var program = new Program();
 6         while (true)
 7         {
 8             // 不斷地嘗試訪問一段資源。這樣,當多個進程運行的時候,能夠很大機率模擬出現資源訪問衝突。
 9             program.UseResource();
10             await Task.Delay(50);
11         }
12     }
13 
14 
15     private void UseResource()
16     {
17         var mutex = new Mutex(false, "Walterlv.Mutex");
18         mutex.WaitOne();
19 
20         // 正在使用公共資源。
21         // 這裏的一段代碼將沒法重入,即便是兩個不一樣的進程。
22         var path = @"C:\Users\lvyi\Desktop\walterlv.log";
23         Console.WriteLine($"[{DateTime.Now:O}] 開始寫入文件……");
24         File.AppendAllText(path, $"[{DateTime.Now:O}] 開始寫入文件……", Encoding.UTF8);
25         Thread.Sleep(1000);
26         File.AppendAllText(path, $"[{DateTime.Now:O}] 寫入文件完成。", Encoding.UTF8);
27         Console.WriteLine($"[{DateTime.Now:O}] 寫入文件完成。");
28 
29         mutex.ReleaseMutex();
30     }
31 }

注意此程序在兩個進程下的運行效果,明明咱們等待使用資源的時間間隔只有 50 ms,但實際上等待時間是 1000 ms 左右。在關掉其中一個進程以後,間隔恢復到了 50 ms 左右。

這說明 Mutex 的等待在這裏起到了跨進程互斥的做用。

以上代碼在兩個進程下的運行結果

當你須要在是不是第一次建立出來的時候進行一些特殊處理,就使用帶 createdNew 參數的構造函數。

 1  private void UseResource()
 2     {
 3 --      var mutex = new Mutex(false, "Walterlv.Mutex");
 4 ++      var mutex = new Mutex(true, "Walterlv.Mutex", out var createdNew);
 5 
 6 --      mutex.WaitOne();
 7 ++      // 若是這個 Mutex 是由此處建立出來的,即 createdNew 爲 true,說明第一個參數 initiallyOwned 是真的發生了,因而咱們就不須要等待。
 8 ++      // 反之,當 createdNew 爲 false 的時候,說明已經有一個現成的 Mutex 已經存在,咱們在這裏須要等待。
 9 ++      if (!createdNew)
10 ++      {
11 ++          mutex.WaitOne();
12 ++      }
13         ……
14         mutex.ReleaseMutex();
15     }

處理異常狀況

ApplicationException

mutex.ReleaseMutex(); 方法只能被當前擁有它的線程調用,若是某個線程試圖調用這個函數,卻沒有擁有這個 Mutex,就會拋出 ApplicationException

怎樣爲擁有呢?還記得前面構造函數中的 initiallyOwned 參數嗎?就是在指定本身是不是此 Mutex 的擁有者的(實際上咱們還須要使用 createdNew 來輔助驗證這一點)。

當一個線程沒有擁有這個 Mutex 的時候,須要使用 WaitOne 來等待得到這個鎖。

AbandonedMutexException

 1 class Program
 2 {
 3     static async Task Main(string[] args)
 4     {
 5         // 開啓一個線程,在那個線程中丟掉得到的 Mutex。
 6         var thread = new Thread(AbandonMutex);
 7         thread.Start();
 8 
 9         // 不要讓進程退出,不然 Mutex 就會被系統回收。
10         Console.Read();
11     }
12 
13     private static void AbandonMutex()
14     {
15         // 得到一個 Mutex,而後就再也不釋放了。
16         // 因爲此線程會在 WaitOne 執行結束後退出,因此這個 Mutex 就被丟掉了。
17         var mutex = new Mutex(false, "Walterlv.Mutex");
18         mutex.WaitOne();
19     }
20 }

上面的這段代碼,當你第一次運行此進程而且保持此進程不退出的時候並無什麼異樣。可是你再啓動第二個進程實例的話,就會在 WaitOne 那裏收到一個異常 —— AbandonedMutexException

因此若是你不能在一處代碼中使用 try-finally 來確保在得到鎖以後必定會釋放的話,那麼強烈建議在 WaitOne 的時候捕獲異常。順便提醒,try-finally 中不能有異步代碼,你能夠參見:在有 UI 線程參與的同步鎖(如 AutoResetEvent)內部使用 await 可能致使死鎖

也就是說,當你須要等待的時候,catch 一下異常。在 catch 完以後,你並不須要再次使用 WaitOne 來等待,由於即使發生了異常,你也依然得到了鎖。這一點你能夠經過調用 ReleaseMutex 來驗證,由於前面咱們說了只有擁有鎖的線程才能夠釋放鎖。

 1 private static void WaitOne()
 2 {
 3     var mutex = new Mutex(false, "Walterlv.Mutex");
 4     try
 5     {
 6         mutex.WaitOne();
 7     }
 8     catch (AbandonedMutexException ex)
 9     {
10         Console.WriteLine("發現被遺棄的鎖");
11     }
12     Console.WriteLine("得到了鎖");
13 }

參考資料

blog bulletin

本文會常常更新,請閱讀原文: https://blog.walterlv.com/post/mutex-in-dotnet.html ,以免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。

相關文章
相關標籤/搜索