.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 ,以免陈旧错误知识的误导,同时有更好的阅读体验。

相关文章
相关标签/搜索