Mutex.TryOpenExisting()是否线程安全?为何代码中_mutex会出现null值?
Mutex.TryOpenExisting线程安全性及变量null问题解析
问题说明
在使用Mutex.TryOpenExisting()时,确认该方法的线程安全性,同时代码偶尔出现_mutex变量为null的错误,相关代码如下:
public class MutexNamedSystemDemo { private const string _mutexName = @"Global\{C7F2DE44-8927-4B01-B8E1-D8F158A483A8}"; private static Mutex _mutex; private static bool _intiallyOwned = false; private static int _count = 0; /*public MutexNamedSystemDemo() { _mutex = new Mutex(_intiallyOwned, _mutexName); }*/ public void Main() { if (Mutex.TryOpenExisting(_mutexName, out _mutex) == false) _mutex = new Mutex(_intiallyOwned, _mutexName); } }
核心解答
1. Mutex.TryOpenExisting的线程安全性
Mutex.TryOpenExisting()本身是线程安全的——它内部会正确处理系统级互斥体的并发访问,不会因多线程调用导致系统互斥体状态损坏。但这不代表你代码中对静态变量_mutex的访问是线程安全的。
2. _mutex为null的根本原因
问题出在静态变量_mutex的并发读写未加同步保护:
- 当多个线程同时进入
Main()方法时,可能出现以下时序:- 线程A调用
Mutex.TryOpenExisting()返回false,准备执行_mutex = new Mutex(...); - 线程B同时调用
Mutex.TryOpenExisting(),此时线程A还未完成互斥体实例化,因此也返回false,同样准备执行赋值; - 极端情况下,线程调度可能导致赋值操作中断,或多线程赋值互相覆盖;更关键的是,无同步的静态变量读写无法保证内存可见性和原子性,部分线程可能读到未初始化的
null。
- 线程A调用
3. 线程安全的修复方案
给静态变量_mutex的初始化逻辑加上同步锁,确保同一时间只有一个线程能完成初始化:
public class MutexNamedSystemDemo { private const string _mutexName = @"Global\{C7F2DE44-8927-4B01-B8E1-D8F158A483A8}"; private static Mutex _mutex; private static bool _intiallyOwned = false; private static int _count = 0; // 静态锁对象,用于同步初始化流程 private static readonly object _initLock = new object(); public void Main() { // 双重检查锁定模式,兼顾线程安全与性能 if (_mutex == null) { lock (_initLock) { if (_mutex == null) { if (!Mutex.TryOpenExisting(_mutexName, out _mutex)) { _mutex = new Mutex(_intiallyOwned, _mutexName); } } } } } }
- 双重检查锁定既避免了每次访问都加锁的性能损耗,又保证了初始化过程的线程安全;
- .NET 2.0及以后版本中,
lock语句会自动保证内存可见性,无需额外给_mutex添加volatile修饰符。
内容的提问来源于stack exchange,提问作者Alireza Molaei
相关产品推荐
相关产品推荐

