当前位置: 首页 > news >正文

.NET 中使用 Mutex 进行跨越进程边界的同步

Mutex 是 Mutual Exclusion 的缩写,是互斥锁,用于防止两个线程同时对计算机上的同一个资源进行访问。不过相比于其他互斥的方式,Mutex 能够跨越线程边界。


本文内容

      • Mutex 是什么?
      • 简单的 Mutex(不能跨进程互斥)
      • 创建跨进程互斥的 Mutex
      • 处理异常情况
        • ApplicationException
        • AbandonedMutexException
        • 参考资料

Mutex 是什么?

与其他线程同步的方式一样,Mutex 也提供对资源的互斥访问;不过 Mutex 使用的系统资源会比 Monitor 更多,而 Monitor 就是实现 C# 中 lock 关键字所用的锁。

用更多的系统资源,带来更强大的功能 —— Mutex 能进行跨越应用程序域边界的封送,能进行跨越进程边界的线程同步。

简单的 Mutex(不能跨进程互斥)

最简单的 Mutex 的使用方法就是直接 new 出来,然后使用 Wait 进行等待,使用 ReleaseMutex 进行释放。

private readonly Mutex _mutex = new Mutex();

private void UseResource()
{
    _mutex.WaitOne();
    
    // 等待一小段时间,假装正在使用公共资源。这里的一段代码在单个进程之内将无法重入。
    Thread.Sleep(500);

    _mutex.ReleaseMutex();
}

参数中有一个 initiallyOwned 参数,如果指定为 true 表示创建这个 Mutex 的线程拥有这个资源(不需要等待),当这个线程调用 ReleaseMutex 之后其他线程的 WaitOne 才会生效。

不过这种方式不能达到跨进程同步的效果,所以实际上本文并不会过多描述这种互斥方式。

创建跨进程互斥的 Mutex

要创建跨进程互斥的 Mutex,必须要给 Mutex 指定名称。

使用 new Mutex(false, "Walterlv.Mutex") 创建一个命名的互斥锁,以便进行跨进程的资源互斥访问。

在使用这个构造函数重载的时候,第一个参数 initiallyOwned 建议的取值为 false。因为当你指定为 true 时,说明你希望此线程是初始创建此 Mutex 的线程,然而由于你是直接 new 出来的,所以你实质上是无法得知你到底是不是第一个 new 出来的。

class Program
{
    static async Task Main(string[] args)
    {
        var program = new Program();
        while (true)
        {
            // 不断地尝试访问一段资源。这样,当多个进程运行的时候,可以很大概率模拟出现资源访问冲突。
            program.UseResource();
            await Task.Delay(50);
        }
    }


    private void UseResource()
    {
        var mutex = new Mutex(false, "Walterlv.Mutex");
        mutex.WaitOne();

        // 正在使用公共资源。
        // 这里的一段代码将无法重入,即使是两个不同的进程。
        var path = @"C:\Users\lvyi\Desktop\walterlv.log";
        Console.WriteLine($"[{DateTime.Now:O}] 开始写入文件……");
        File.AppendAllText(path, $"[{DateTime.Now:O}] 开始写入文件……", Encoding.UTF8);
        Thread.Sleep(1000);
        File.AppendAllText(path, $"[{DateTime.Now:O}] 写入文件完成。", Encoding.UTF8);
        Console.WriteLine($"[{DateTime.Now:O}] 写入文件完成。");

        mutex.ReleaseMutex();
    }
}

注意此程序在两个进程下的运行效果,明明我们等待使用资源的时间间隔只有 50 ms,但实际上等待时间是 1000 ms 左右。在关掉其中一个进程之后,间隔恢复到了 50 ms 左右。

这说明 Mutex 的等待在这里起到了跨进程互斥的作用。

以上代码在两个进程下的运行结果

当你需要在是否是第一次创建出来的时候进行一些特殊处理,就使用带 createdNew 参数的构造函数。

    private void UseResource()
    {
--      var mutex = new Mutex(false, "Walterlv.Mutex");
++      var mutex = new Mutex(true, "Walterlv.Mutex", out var createdNew);

--      mutex.WaitOne();
++      // 如果这个 Mutex 是由此处创建出来的,即 createdNew 为 true,说明第一个参数 initiallyOwned 是真的发生了,于是我们就不需要等待。
++      // 反之,当 createdNew 为 false 的时候,说明已经有一个现成的 Mutex 已经存在,我们在这里需要等待。
++      if (!createdNew)
++      {
++          mutex.WaitOne();
++      }
        ……
        mutex.ReleaseMutex();
    }

处理异常情况

ApplicationException

mutex.ReleaseMutex(); 方法只能被当前拥有它的线程调用,如果某个线程试图调用这个函数,却没有拥有这个 Mutex,就会抛出 ApplicationException

怎样为拥有呢?还记得前面构造函数中的 initiallyOwned 参数吗?就是在指定自己是否是此 Mutex 的拥有者的(实际上我们还需要使用 createdNew 来辅助验证这一点)。

当一个线程没有拥有这个 Mutex 的时候,需要使用 WaitOne 来等待获得这个锁。

AbandonedMutexException

class Program
{
    static async Task Main(string[] args)
    {
        // 开启一个线程,在那个线程中丢掉获得的 Mutex。
        var thread = new Thread(AbandonMutex);
        thread.Start();

        // 不要让进程退出,否则 Mutex 就会被系统回收。
        Console.Read();
    }

    private static void AbandonMutex()
    {
        // 获得一个 Mutex,然后就不再释放了。
        // 由于此线程会在 WaitOne 执行结束后退出,所以这个 Mutex 就被丢掉了。
        var mutex = new Mutex(false, "Walterlv.Mutex");
        mutex.WaitOne();
    }
}

上面的这段代码,当你第一次运行此进程并且保持此进程不退出的时候并没有什么异样。但是你再启动第二个进程实例的话,就会在 WaitOne 那里收到一个异常 —— AbandonedMutexException

所以如果你不能在一处代码中使用 try-finally 来确保在获得锁之后一定会释放的话,那么强烈建议在 WaitOne 的时候捕获异常。顺便提醒,try-finally 中不能有异步代码,你可以参见:在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁。

也就是说,当你需要等待的时候,catch 一下异常。在 catch 完之后,你并不需要再次使用 WaitOne 来等待,因为即便发生了异常,你也依然获得了锁。这一点你可以通过调用 ReleaseMutex 来验证,因为前面我们说了只有拥有锁的线程才可以释放锁。

private static void WaitOne()
{
    var mutex = new Mutex(false, "Walterlv.Mutex");
    try
    {
        mutex.WaitOne();
    }
    catch (AbandonedMutexException ex)
    {
        Console.WriteLine("发现被遗弃的锁");
    }
    Console.WriteLine("获得了锁");
}

参考资料

  • Mutexes - Microsoft Docs
  • Mutex Constructor (System.Threading) - Microsoft Docs
  • Mutex Class (System.Threading) - Microsoft Docs

我的博客会首发于 https://walterlv.com/,而 CSDN 和博客园仅从其中摘选发布,而且一旦发布了就不再更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://blog.csdn.net/wpwalter),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • int? 竟然真的可以是 null!.NET/C# 确定可空值类型 NullableT 实例的真实类型
  • Slack 开发入门之 Incoming Webhooks:往 Slack 的 Channel 中发消息
  • 三值 bool? 进行与或运算后的结果
  • 为什么我们不应该使用微信或者 QQ 作为团队协作的 IM 工具?
  • 通过重写预定义的 Target 来扩展 MSBuild / Visual Studio 的编译过程
  • .NET 中 GetHashCode 的哈希值有多大概率会相同(哈希碰撞)
  • C# 中委托实例的命名规则
  • 在 Target 中获取项目引用的所有依赖(dll/NuGet/Project)的路径
  • 让 MSBuild Target 支持 Clean
  • C#/.NET 如何确认一个路径是否是合法的文件路径
  • 不使用 U 盘等任何工具全新安装 Windows 操作系统
  • C# 永远不会返回的方法真的不会返回
  • CentOS 的终端中如何搜索文件
  • 如何在命令行中监听用户输入文本的改变?
  • 使用 Xamarin 开发 iOS 键盘扩展(含网络访问)
  • Android单元测试 - 几个重要问题
  • golang中接口赋值与方法集
  • JAVA SE 6 GC调优笔记
  • javascript 总结(常用工具类的封装)
  • Java-详解HashMap
  • JS变量作用域
  • Material Design
  • Redis学习笔记 - pipline(流水线、管道)
  • Ruby 2.x 源代码分析:扩展 概述
  • Shell编程
  • vuex 笔记整理
  • 前端面试题总结
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 通过git安装npm私有模块
  • No resource identifier found for attribute,RxJava之zip操作符
  • # 日期待t_最值得等的SUV奥迪Q9:空间比MPV还大,或搭4.0T,香
  • #### go map 底层结构 ####
  • ###STL(标准模板库)
  • #QT(智能家居界面-界面切换)
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (多级缓存)缓存同步
  • (三十五)大数据实战——Superset可视化平台搭建
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转)c++ std::pair 与 std::make
  • (转)母版页和相对路径
  • (转)使用VMware vSphere标准交换机设置网络连接
  • (转载)VS2010/MFC编程入门之三十四(菜单:VS2010菜单资源详解)
  • (转载)虚函数剖析
  • *Django中的Ajax 纯js的书写样式1
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET上SQLite的连接
  • @RestController注解的使用
  • [ linux ] linux 命令英文全称及解释
  • [ vulhub漏洞复现篇 ] Celery <4.0 Redis未授权访问+Pickle反序列化利用
  • [16/N]论得趣
  • [ACTF2020 新生赛]Upload 1