后端

C# Mutex类WaitOne方法的使用详解与注意事项

TRAE AI 编程助手

引言

在多线程编程中,线程同步是一个至关重要的话题。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# 中实现线程同步的重要工具,特别适用于需要跨进程同步的场景。使用时需要注意:

  1. 始终在 finally 块中释放 Mutex,确保异常情况下也能正确释放
  2. 合理设置超时时间,避免无限期等待
  3. 注意线程亲和性,只能在获得 Mutex 的线程中释放它
  4. 处理异常情况,如 AbandonedMutexException
  5. 权衡性能,Mutex 开销较大,进程内同步优先考虑 lock 或 Monitor

通过正确使用 WaitOne 方法,可以有效地实现线程安全的资源访问,构建健壮的多线程应用程序。在实际开发中,应根据具体需求选择合适的同步机制,确保程序的正确性和性能。

(此内容由 AI 辅助生成,仅供参考)