引言
在多线程编程中,线程同步是一个至关重要的话题。C# 提供了多种同步机制,其中 Mutex(互斥体)是一种强大的同步原语,可以跨进程进行线程同步。本文将深入探讨 Mutex 类的 WaitOne 方法,这是使用 Mutex 进行线程同步的核心方法。
Mutex 类简介
Mutex 是 "Mutual Exclusion" 的缩写,它是一种同步原语,用于在多个线程之间提供对共享资源的互斥访问。与 Monitor 和 lock 语句不同,Mutex 可以跨应用程序域、进程甚至机器边界(在命名 Mutex 的情况下)进行同步。
Mutex 的主要特点
| 特点 | 描述 |
|---|---|
| 跨进程同步 | 命名 Mutex 可以在不同进程间共享 |
| 线程亲和性 | 只有获得 Mutex 的线程才能释放它 |
| 递归获取 | 同一线程可以多次获取同一 Mutex |
| 系统资源 | Mutex 是操作系统内核对象,开销相对较大 |
WaitOne 方法详解
WaitOne 是 Mutex 类最重要的方法之一,用于请求 Mutex 的所有权。当线程调用 WaitOne 时,它会阻塞直到获得 Mutex 或超时。
方法重载
WaitOne 方法有多个重载版本:
// 无限期等待
public virtual bool WaitOne();
// 指定超时时间(毫秒)
public virtual bool WaitOne(int millisecondsTimeout);
// 指定超时时间(TimeSpan)
public virtual bool WaitOne(TimeSpan timeout);
// 指定超时时间和是否退出上下文域
public virtual bool WaitOne(int millisecondsTimeout, bool exitContext);
public virtual bool WaitOne(TimeSpan timeout, bool exitContext);返回值说明
- true:成功获得 Mutex 的所有权
- false:在指定的超时时间内未能获得 Mutex
基本使用示例
示例 1:基本的线程同步
using System;
using System.Threading;
class Program
{
private static Mutex mutex = new Mutex();
private static int sharedResource = 0;
static void Main()
{
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(WorkerThread);
threads[i].Name = $"Thread-{i + 1}";
threads[i].Start();
}
foreach (Thread t in threads)
{
t.Join();
}
Console.WriteLine($"Final value: {sharedResource}");
}
static void WorkerThread()
{
for (int i = 0; i < 1000; i++)
{
mutex.WaitOne(); // 获取 Mutex
try
{
sharedResource++;
Console.WriteLine($"{Thread.CurrentThread.Name} incremented to {sharedResource}");
}
finally
{
mutex.ReleaseMutex(); // 释放 Mutex
}
}
}
}示例 2:使用超时机制
using System;
using System.Threading;
class TimeoutExample
{
private static Mutex mutex = new Mutex();
static void Main()
{
Thread t1 = new Thread(LongRunningTask);
Thread t2 = new Thread(TimeoutTask);
t1.Start();
Thread.Sleep(100); // 确保 t1 先获得 Mutex
t2.Start();
t1.Join();
t2.Join();
}
static void LongRunningTask()
{
mutex.WaitOne();
try
{
Console.WriteLine("Long task started");
Thread.Sleep(5000); // 模拟长时间操作
Console.WriteLine("Long task completed");
}
finally
{
mutex.ReleaseMutex();
}
}
static void TimeoutTask()
{
Console.WriteLine("Attempting to acquire mutex with 2 second timeout...");
if (mutex.WaitOne(2000)) // 2秒超时
{
try
{
Console.WriteLine("Successfully acquired mutex");
}
finally
{
mutex.ReleaseMutex();
}
}
else
{
Console.WriteLine("Failed to acquire mutex within timeout period");
}
}
}示例 3:命名 Mutex 实现进程间同步
using System;
using System.Threading;
class InterProcessSync
{
static void Main()
{
const string mutexName = "Global\\MyApplicationMutex";
bool createdNew;
using (Mutex mutex = new Mutex(false, mutexName, out createdNew))
{
Console.WriteLine($"Process {Environment.ProcessId} attempting to acquire mutex...");
if (mutex.WaitOne(TimeSpan.FromSeconds(5)))
{
try
{
Console.WriteLine($"Process {Environment.ProcessId} acquired mutex");
Console.WriteLine("Press any key to release mutex...");
Console.ReadKey();
}
finally
{
mutex.ReleaseMutex();
Console.WriteLine($"Process {Environment.ProcessId} released mutex");
}
}
else
{
Console.WriteLine($"Process {Environment.ProcessId} could not acquire mutex");
}
}
}
}高级用法
递归获取 Mutex
Mutex 支持递归获取,即同一线程可以多次调用 WaitOne 而不会死锁:
using System;
using System.Threading;
class RecursiveMutexExample
{
private static Mutex mutex = new Mutex();
private static int recursionCount = 0;
static void Main()
{
RecursiveMethod(3);
}
static void RecursiveMethod(int depth)
{
mutex.WaitOne();
recursionCount++;
try
{
Console.WriteLine($"Recursion depth: {depth}, Mutex acquired {recursionCount} times");
if (depth > 0)
{
RecursiveMethod(depth - 1);
}
}
finally
{
mutex.ReleaseMutex();
recursionCount--;
Console.WriteLine($"Released mutex, remaining count: {recursionCount}");
}
}
}使用 WaitHandle.WaitAll 等待多个 Mutex
using System;
using System.Threading;
class MultiMutexExample
{
static void Main()
{
Mutex[] mutexes = new Mutex[3];
for (int i = 0; i < mutexes.Length; i++)
{
mutexes[i] = new Mutex();
}
Thread worker = new Thread(() =>
{
Console.WriteLine("Attempting to acquire all mutexes...");
// 等待所有 Mutex
if (WaitHandle.WaitAll(mutexes, 5000))
{
try
{
Console.WriteLine("All mutexes acquired successfully");
Thread.Sleep(1000);
}
finally
{
foreach (var mutex in mutexes)
{
mutex.ReleaseMutex();
}
Console.WriteLine("All mutexes released");
}
}
else
{
Console.WriteLine("Failed to acquire all mutexes");
}
});
worker.Start();
worker.Join();
}
}注意事项与最佳实践
1. 始终在 finally 块中释放 Mutex
mutex.WaitOne();
try
{
// 临界区代码
}
finally
{
mutex.ReleaseMutex(); // 确保 Mutex 被释放
}2. 避免长时间持有 Mutex
长时间持有 Mutex 会导致其他线程长时间等待,影响程序性能:
// 不好的做法
mutex.WaitOne();
try
{
// 执行大量计算
ComplexCalculation();
// 执行 I/O 操作
ReadFromDatabase();
}
finally
{
mutex.ReleaseMutex();
}
// 好的做法
mutex.WaitOne();
try
{
// 只保护共享资源的访问
UpdateSharedState();
}
finally
{
mutex.ReleaseMutex();
}
// 在 Mutex 外执行耗时操作
ComplexCalculation();
ReadFromDatabase();3. 处理 AbandonedMutexException
当拥有 Mutex 的线程终止而没有释放 Mutex 时,会抛出 AbandonedMutexException:
try
{
mutex.WaitOne();
try
{
// 临界区代码
}
finally
{
mutex.ReleaseMutex();
}
}
catch (AbandonedMutexException ex)
{
Console.WriteLine("Mutex was abandoned by another thread");
// Mutex 的所有权已自动转移到当前线程
try
{
// 可能需要清理或重置共享资源的状态
CleanupSharedResource();
}
finally
{
mutex.ReleaseMutex();
}
}4. 使用命名 Mutex 时的权限问题
在 Windows 上创建全局命名 Mutex 可能需要特殊权限:
using System.Security.AccessControl;
using System.Security.Principal;
static Mutex CreateSecureMutex(string name)
{
var securitySettings = new MutexSecurity();
var everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var rule = new MutexAccessRule(everyone,
MutexRights.FullControl,
AccessControlType.Allow);
securitySettings.AddAccessRule(rule);
bool createdNew;
return new Mutex(false, name, out createdNew, securitySettings);
}5. 避免死锁
当使用多个 Mutex 时,始终以相同的顺序获取它们:
// 可能导致死锁的代码
// Thread 1
mutex1.WaitOne();
mutex2.WaitOne();
// Thread 2
mutex2.WaitOne();
mutex1.WaitOne();
// 避免死锁的做法
// 所有线程都以相同顺序获取 Mutex
mutex1.WaitOne();
mutex2.WaitOne();性能考虑
Mutex vs 其他同步机制
graph TD
A[同步机制选择] --> B{需要跨进程同步?}
B -->|是| C[使用 Mutex]
B -->|否| D{需要递归锁?}
D -->|是| E[使用 Mutex 或 ReaderWriterLockSlim]
D -->|否| F{性能要求高?}
F -->|是| G[使用 lock/Monitor 或 SemaphoreSlim]
F -->|否| H[使用任意合适的机制]
性能对比
| 同步机制 | 相对性能 | 跨进程 | 递归支持 | 适用场景 |
|---|---|---|---|---|
| lock/Monitor | 最快 | 否 | 是 | 进程内简单同步 |
| SemaphoreSlim | 快 | 否 | 否 | 限制并发访问数 |
| Mutex | 慢 | 是 | 是 | 跨进程同步 |
| Semaphore | 最慢 | 是 | 否 | 跨进程限制并发 |
常见错误与解决方案
错误 1:未释放 Mutex
// 错误代码
void BadMethod()
{
mutex.WaitOne();
if (someCondition)
{
return; // Mutex 未释放!
}
mutex.ReleaseMutex();
}
// 正确代码
void GoodMethod()
{
mutex.WaitOne();
try
{
if (someCondition)
{
return;
}
}
finally
{
mutex.ReleaseMutex();
}
}错误 2:在错误的线程释放 Mutex
// 错误:尝试在不拥有 Mutex 的线程中释放它
Thread t1 = new Thread(() => mutex.WaitOne());
Thread t2 = new Thread(() => mutex.ReleaseMutex()); // 将抛出异常
// 正确:在同一线程中获取和释放
Thread t = new Thread(() =>
{
mutex.WaitOne();
try
{
// 工作
}
finally
{
mutex.ReleaseMutex();
}
});错误 3:忽略 WaitOne 的返回值
// 错误:假设总是能获得 Mutex
mutex.WaitOne(1000);
// 继续执行,即使未获得 Mutex
// 正确:检查返回值
if (mutex.WaitOne(1000))
{
try
{
// 只有获得 Mutex 才执行
}
finally
{
mutex.ReleaseMutex();
}
}
else
{
// 处理超时情况
}实际应用场景
场景 1:单实例应用程序
class SingleInstanceApp
{
static void Main()
{
const string appName = "MyUniqueApplication";
bool createdNew;
using (Mutex mutex = new Mutex(true, appName, out createdNew))
{
if (!createdNew)
{
Console.WriteLine("Application is already running!");
return;
}
Console.WriteLine("Application started successfully");
RunApplication();
}
}
static void RunApplication()
{
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}场景 2:资源池管理
class ResourcePool
{
private readonly Mutex[] resourceMutexes;
private readonly object[] resources;
public ResourcePool(int size)
{
resourceMutexes = new Mutex[size];
resources = new object[size];
for (int i = 0; i < size; i++)
{
resourceMutexes[i] = new Mutex();
resources[i] = new object(); // 初始化资源
}
}
public object AcquireResource(int timeout = Timeout.Infinite)
{
int index = WaitHandle.WaitAny(resourceMutexes, timeout);
if (index == WaitHandle.WaitTimeout)
{
return null; // 超时
}
return resources[index];
}
public void ReleaseResource(object resource)
{
int index = Array.IndexOf(resources, resource);
if (index >= 0)
{
resourceMutexes[index].ReleaseMutex();
}
}
}总结
Mutex 的 WaitOne 方法是 C# 中实现线程同步的重要工具,特别适用于需要跨进程同步的场景。使用时需要注意:
- 始终在 finally 块中释放 Mutex,确保异常情况下也能正确释放
- 合理设置超时时间,避免无限期等待
- 注意线程亲和性,只能在获得 Mutex 的线程中释放它
- 处理异常情况,如 AbandonedMutexException
- 权衡性能,Mutex 开销较大,进程内同步优先考虑 lock 或 Monitor
通过正确使用 WaitOne 方法,可以有效地实现线程安全的资源访问,构建健壮的多线程应用程序。在实际开发中,应根据具体需求选择合适的同步机制,确保程序的正确性和性能。
(此内容由 AI 辅助生成,仅供参考)