1. 为什么我们需要互斥锁?
在C#多线程编程中,互斥锁(Mutex)是最基础也最重要的同步机制之一。想象一下这样的场景:你正在开发一个银行转账系统,两个线程同时操作同一个账户余额。如果没有保护机制,可能会出现这样的情况:
code复制线程A读取余额:1000元
线程B读取余额:1000元
线程A计算新余额:1000 - 200 = 800
线程B计算新余额:1000 + 300 = 1300
线程A写入新余额:800
线程B写入新余额:1300
最终账户余额变成了1300元,而实际上应该是1100元(1000-200+300)。这就是典型的竞态条件(Race Condition)问题,而互斥锁正是解决这类问题的利器。
1.1 互斥锁的核心特性
互斥锁之所以能解决上述问题,是因为它具有三个关键特性:
- 互斥性:同一时刻只允许一个线程持有锁
- 原子性:获取和释放锁的操作是不可分割的
- 可见性:锁释放前对共享变量的修改对其他线程可见
在C#中,Mutex类是System.Threading命名空间提供的互斥锁实现,它实际上是操作系统级别提供的同步原语(Synchronization Primitive)的封装。这意味着:
- 它比C#的lock关键字(Monitor类)更重量级
- 它可以跨进程同步(而lock只能在同一个进程内)
- 它有名字,可以通过名字在不同进程间共享
1.2 何时应该选择Mutex?
虽然Mutex功能强大,但并不是所有场景都适合使用。以下是几种典型的使用场景:
- 跨进程同步:当需要协调多个进程对共享资源(如文件、硬件设备)的访问时
- 长时间持有锁:当临界区代码执行时间较长时(lock关键字适合短时间持有)
- 高级同步需求:如递归获取、超时控制等
相比之下,对于简单的单进程内线程同步,C#的lock关键字通常是更好的选择,因为它更轻量级且不易出错。
2. Mutex的基本使用方式
2.1 创建和初始化Mutex
在C#中创建Mutex有几种常见方式:
csharp复制// 方式1:创建未命名的局部Mutex
var mutex = new Mutex();
// 方式2:创建命名的Mutex(可用于跨进程同步)
var namedMutex = new Mutex(false, "Global\\MyAppMutex");
// 方式3:尝试获取已存在的Mutex
Mutex existingMutex;
bool createdNew;
existingMutex = new Mutex(true, "Global\\MyAppMutex", out createdNew);
这里有几个关键点需要注意:
- 命名规则:命名Mutex时,"Global\"前缀表示系统全局Mutex(所有会话可见),"Local\"表示仅当前会话可见
- 初始拥有权:构造函数第一个参数表示创建线程是否初始拥有该Mutex
- createdNew参数:可以判断是新建了Mutex还是获取了已存在的
提示:在跨进程场景中,Mutex名称应该足够唯一以避免冲突,通常建议包含公司名、应用名等信息。
2.2 获取和释放Mutex
Mutex的基本使用模式如下:
csharp复制Mutex mutex = new Mutex();
try
{
// 尝试获取锁,最多等待1秒
if (mutex.WaitOne(1000))
{
// 临界区代码
// 操作共享资源
}
else
{
// 获取锁超时处理
Console.WriteLine("获取锁超时");
}
}
finally
{
// 释放锁
mutex.ReleaseMutex();
}
这里有几个关键注意事项:
- 总是使用try-finally:确保锁一定会被释放,避免死锁
- 超时处理:WaitOne方法可以指定超时时间,避免无限等待
- 递归获取:同一线程可以多次获取同一个Mutex,但必须释放相同次数
2.3 常见错误模式
在实际使用中,开发者常犯以下几种错误:
错误1:忘记释放Mutex
csharp复制mutex.WaitOne();
// 临界区代码
// 忘记调用ReleaseMutex()
这会导致其他线程永远无法获取该Mutex,造成死锁。
错误2:跨线程释放
csharp复制void Thread1()
{
mutex.WaitOne();
// ...
}
void Thread2()
{
// ...
mutex.ReleaseMutex(); // 错误!必须在获取的线程释放
}
Mutex的获取和释放必须在同一个线程内完成。
错误3:异常处理不当
csharp复制mutex.WaitOne();
try
{
// 可能抛出异常的代码
}
catch
{
// 处理异常但没有释放Mutex
}
应该在finally块中释放Mutex,确保异常情况下也能释放。
