C# Mutex 锁 使用详解

news/2025/2/8 20:39:48 标签: c#, 开发语言

总目录


前言

在C#中,Mutex(互斥锁)是一种跨进程的同步机制,用于控制多个线程或进程对共享资源的访问。与 Monitor 和 lock 不同,Mutex 可以跨越进程边界进行同步,这使得它非常适合需要跨进程同步的场景。本文将详细介绍 Mutex 的工作原理、主要方法及其使用示例。


一、Mutex 类概述

Mutex 类位于 System.Threading 命名空间中,提供了比 lock 和 Monitor 更高级别的同步机制,支持跨进程同步。它的主要功能是通过提供一个互斥锁来控制对共享资源的访问,确保同一时刻只有一个线程或进程可以持有该锁。

1. 基本锁机制

Mutex通过WaitOneReleaseMutex方法实现锁的获取和释放。

using System.Threading;

// 创建未命名的 Mutex(仅进程内有效)
Mutex mutex = new Mutex();

// 创建命名的跨进程 Mutex(Windows 全局命名空间)
Mutex namedMutex = new Mutex(initiallyOwned: false, "Global\\MyAppMutex");

try
{
    // 尝试获取锁(阻塞或超时)
    if (mutex.WaitOne(timeout: TimeSpan.FromSeconds(5)))
    {
        // 临界区:操作共享资源
    }
    else
    {
        Console.WriteLine("获取锁超时!");
    }
}
catch (AbandonedMutexException ex)
{
    // 处理其他线程未释放锁的情况
    Console.WriteLine("检测到被遗弃的 Mutex!");
}
finally
{
    mutex.ReleaseMutex(); // 释放锁
    mutex.Close();        // 显式关闭(或使用 using 块)
}

2. 核心特性

  • 命名Mutex:通过名称跨进程共享(如"Global\\MyMutex"),常用于多进程同步。
  • 未命名Mutex:仅在同一进程内有效。
  • 等待和释放:提供等待锁的方法(如 WaitOne)和释放锁的方法(如 ReleaseMutex)。

二、主要方法和属性

1. 构造函数

Mutex 类提供了两个构造函数:

  • 无参数构造函数:创建一个未命名Mutex。
	var mutex = new Mutex();
  • 带参数的构造函数:创建一个命名Mutex,并指定是否初始拥有该互斥量。
	bool createdNew;
	var mutex = new Mutex(initiallyOwned: true, name: "MyGlobalMutex", createdNew: out createdNew);
	public Mutex(
	    bool initiallyOwned,    // 初始是否由创建线程拥有
	    string name,            // 命名 Mutex 的唯一标识
	    out bool createdNew     // 输出参数,指示是否创建了新 Mutex
	)

参数说明

  • initiallyOwned:若为 true,创建线程直接获得锁所有权。

  • name

    • 命名规则:支持系统级名称(如 “Global\\MyMutex”)或本地名称。
      • Windows:支持 Global\\Local\\ 前缀。
      • Linux/macOS:名称会被视为文件路径(如 /MyAppMutex),需确保路径可写权限。
    • 跨进程同步:同一名称的 Mutex 在系统范围内共享。
    • 特殊前缀:
      • Global\\:Windows 全局命名空间(跨用户会话)。
      • Local\\:当前用户会话内的命名空间(默认)。
    • null 或空字符串:
      • 如果 name 为 null 或空字符串,则将创建一本地 Mutex 对象,只在进程内有效。
  • createdNew:用于判断是否成功创建新 Mutex(避免重复初始化)。

2. WaitOne 和 ReleaseMutex 方法

  • WaitOne 方法用于等待获取互斥量。如果当前没有其他线程或进程持有该互斥量,则立即返回;否则,调用线程将被阻塞,直到获得锁。
  • ReleaseMutex 方法用于释放当前线程持有的互斥量。必须确保在每次成功获取互斥量后都调用此方法,否则会导致死锁。

示例:基本用法

var mutex = new Mutex();

mutex.WaitOne(); // 等待获取互斥量
try
{
    // 临界区代码
    Console.WriteLine("Mutex acquired.");
}
finally
{
    mutex.ReleaseMutex(); // 释放互斥量
}

3. Close 方法

Close 方法用于释放与 Mutex 对象关联的所有资源。通常在不再需要互斥量时调用此方法。

示例:关闭互斥量

mutex.Close();

4. 安全性控制

通过 MutexSecurity 设置访问权限(仅 Windows 有效):

var mutexSecurity = new MutexSecurity();
mutexSecurity.AddAccessRule(
    new MutexAccessRule(
        "Everyone",
        MutexRights.FullControl,
        AccessControlType.Allow));

using (Mutex mutex = new Mutex(
    initiallyOwned: false,
    name: "Global\\SecuredMutex",
    createdNew: out _,
    mutexSecurity: mutexSecurity))
{
    // 使用受权限控制的 Mutex
}

5. 检测锁状态

通过 WaitOne 的返回值判断是否成功获取锁:

if (mutex.WaitOne(0)) // 立即返回,不阻塞
{
    // 成功获取锁
}
else
{
    // 锁已被其他线程/进程占用
}

三、异常与错误处理

1. AbandonedMutexException

当其他线程未释放锁直接退出时,尝试获取该锁会触发此异常:

try
{
    mutex.WaitOne();
}
catch (AbandonedMutexException ex)
{
    // 处理被遗弃的锁(可能需要恢复数据一致性)
    Console.WriteLine($"Mutex {ex.MutexName} 被异常释放!");
    mutex.ReleaseMutex(); // 手动释放
}

2. 权限不足

创建全局命名(即使用Global\\) Mutex 可能需要管理员权限:

try
{
    var mutex = new Mutex(false, "Global\\AdminMutex");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("需要管理员权限!");
}

四、使用场景、注意事项与示例

1. 使用场景

  • 防止重复启动:确保应用程序在同一时间只能运行一个实例。
  • 跨进程同步:确保多个进程不会同时访问共享资源。
    • 如 跨进程文件访问:确保多个进程写入同一文件时互斥。
    • 如 共享硬件控制:协调多个进程访问同一硬件设备(如打印机)。
  • 复杂同步需求:处理涉及多个线程和进程的复杂同步问题。
  • 分布式系统协调:通过Mutex实现简单的分布式锁(需谨慎设计)。

2. 注意事项

  • 命名规则:命名Mutex需唯一,避免冲突(如使用GUID)。

  • 资源释放

    • 必须成对调用 WaitOne 和 ReleaseMutex。
    • 必须调用ReleaseMutex释放锁,否则其他线程/进程无法获取。
    • 使用using块或 try-finally + Close方法确保资源释放。
  • 权限问题:跨进程Mutex可能需要管理员权限(尤其是Global命名空间)。

  • 死锁风险:避免跨进程锁的嵌套使用,容易导致复杂死锁。

    • 跨进程锁的嵌套容易导致不可预测的死锁。
    • 设置合理的超时时间(如 WaitOne(5000))。
  • 跨平台限制:命名Mutex在Linux/macOS上行为可能与Windows不同(如命名格式)。

  • 替代方案:使用Mutex的代价是较高的性能开销(涉及内核模式切换),因此在轻量级场景中应优先选择 Monitor 或 Semaphore。

    • Mutex 只需考虑实现进程间的同步,进程内请考虑 Monitor/lock。
  • 性能优化:避免频繁使用跨进程Mutex,尽量缩小临界区范围。

  • 何时用Mutex:需要跨进程同步或Monitor无法满足复杂需求时。

    • 需要系统级锁的场景(如单实例应用、跨进程资源控制)。
    • 对同步可靠性要求极高的任务(如硬件访问)。
    • 简单分布式锁的原型设计。

合理使用Mutex可以有效解决复杂的同步问题,但其系统级开销需谨慎权衡。

3. 单一进程中的mutex 示例

以下是一个简单的例子,展示了如何在单一进程中使用互斥量来保护共享资源。

示例:单一进程中的互斥量

using System;
using System.Threading;

class Program
{
    // 共享资源(需要线程安全访问)
    private static int _sharedCounter = 0;
    // 创建Mutex对象(进程内线程同步)
    private static Mutex _mutex = new Mutex();

    static void Main()
    {
        Console.WriteLine("=== 单进程多线程Mutex演示 ===");
        Console.WriteLine("请输入要创建的线程数量:");
        
        if (!int.TryParse(Console.ReadLine(), out int threadCount) || threadCount < 1)
        {
            Console.WriteLine("输入无效,默认使用4个线程");
            threadCount = 4;
        }

        // 启动多个工作线程
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++)
        {
            threads[i] = new Thread(IncrementCounter);
            threads[i].Start(i + 1); // 传递线程ID
        }

        // 等待所有线程完成
        foreach (var thread in threads)
        {
            thread.Join();
        }

        Console.WriteLine($"\n最终结果(理论值:{threadCount * 1000}):");
        Console.WriteLine($"实际值:{_sharedCounter}");
        Console.WriteLine("是否出现竞争条件? " + (_sharedCounter == threadCount * 1000 ? "否" : "是"));
        Console.ReadLine();
    }

    static void IncrementCounter(object threadId)
    {
        try
        {
            for (int i = 0; i < 1000; i++)
            {
                // 通过Mutex保护临界区
                _mutex.WaitOne();
                try
                {
                    // 安全地操作共享资源
                    _sharedCounter++;
                }
                finally
                {
                    _mutex.ReleaseMutex();
                }

                // 模拟非临界区操作(不需要锁)
                Thread.Sleep(new Random().Next(1, 5));
            }
            Console.WriteLine($"线程 {threadId} 完成");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"线程 {threadId} 错误:{ex.Message}");
        }
    }
}

代码解析

1. 关键组件

组件说明
_sharedCounter被多个线程共享的计数器,用于演示资源竞争问题
_mutex进程内的Mutex对象,用于控制对_sharedCounter的访问
IncrementCounter线程方法,包含需要保护的临界区代码

2. 执行流程

  1. 用户输入线程数量:创建指定数量的线程
  2. 每个线程执行1000次累加:
    • 使用WaitOne()获取锁
    • 安全修改_sharedCounter
    • 使用ReleaseMutex()释放锁
  3. 显示最终结果:
    • 理论值 = 线程数量 × 1000
    • 实际值展示是否发生资源竞争

运行结果对比

场景1:使用Mutex(线程安全)

=== 单进程多线程Mutex演示 ===
请输入要创建的线程数量:
4
线程 4 完成
线程 1 完成
线程 3 完成
线程 2 完成

最终结果(理论值:4000):
实际值:4000
是否出现竞争条件? 否

场景2:注释掉Mutex代码(模拟资源竞争)

// _mutex.WaitOne();
// try
// {
    _sharedCounter++;
// }
// finally
// {
//     _mutex.ReleaseMutex();
// }

结果:

实际值:3876  // 每次运行结果不同
是否出现竞争条件? 是

关键知识点

1. 临界区保护模式

_mutex.WaitOne();  // 进入临界区
try
{
    // 安全操作共享资源
}
finally
{
    _mutex.ReleaseMutex(); // 确保总是释放锁
}

2. 为什么需要try-finally?

  • 保证即使临界区代码抛出异常,锁也会被释放
  • 避免死锁(其他线程永远无法获取锁)

3. 锁粒度控制

  • 需要锁的操作:共享资源修改 (_sharedCounter++)
  • 不需要锁的操作:非共享操作 (Thread.Sleep)

扩展实验建议

1. 观察锁竞争
在WaitOne前添加日志:

Console.WriteLine($"线程 {threadId} 等待锁...");
_mutex.WaitOne();
Console.WriteLine($"线程 {threadId} 获得锁");

2. 尝试非阻塞锁

if (_mutex.WaitOne(0)) // 立即返回是否成功
{
    try { /* 操作 */ }
    finally { _mutex.ReleaseMutex(); }
}
else
{
    Console.WriteLine("获取锁失败,执行其他操作");
}

3. 递归获取测试

// 同一线程多次获取锁
_mutex.WaitOne();
_mutex.WaitOne(); // 能成功吗?
_sharedCounter++;
_mutex.ReleaseMutex();
_mutex.ReleaseMutex(); // 需要释放两次吗?

4. 跨进程中的mutex 示例

1. 示例:防止重复启动的应用程序

以下是一个使用 Mutex 实现 单实例控制台应用程序 的详细案例,包含错误处理、用户提示及窗口激活功能:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    // 用于激活已存在实例的窗口(Windows API)
    [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    private static void Main()
    {
        // 定义唯一的 Mutex 名称(全局命名空间)
        const string mutexName = "Global\\MySingleInstanceConsoleApp";
        bool isNewInstance;

        // 创建 Mutex 并检查是否已存在实例
        using (Mutex mutex = new Mutex(
            initiallyOwned: true,   // 初始拥有权
            name: mutexName,        // 全局唯一名称
            createdNew: out isNewInstance))
        {
            try
            {
                if (!isNewInstance)
                {
                    // 发现已有实例运行
                    Console.WriteLine("程序已在运行!正在激活已有窗口...");
                    ActivateExistingWindow();
                    Thread.Sleep(2000); // 等待用户查看提示
                    return;
                }

                // 主程序逻辑
                Console.Title = "单实例示例程序 (PID: " + Process.GetCurrentProcess().Id + ")";
                Console.WriteLine("程序已启动,输入 'exit' 退出。");
                
                // 模拟工作循环
                string input;
                do
                {
                    Console.Write("> ");
                    input = Console.ReadLine();
                    Console.WriteLine($"处理命令: {input}");
                } while (input?.ToLower() != "exit");
            }
            catch (UnauthorizedAccessException)
            {
                Console.WriteLine("错误:需要管理员权限创建全局 Mutex!");
                Thread.Sleep(3000);
            }
            catch (AbandonedMutexException)
            {
                Console.WriteLine("警告:前一个实例未正常释放 Mutex!");
            }
        }
    }

    /// <summary>
    /// 激活已存在的程序窗口(仅限Windows)
    /// </summary>
    private static void ActivateExistingWindow()
    {
        Process currentProcess = Process.GetCurrentProcess();
        string processName = currentProcess.ProcessName;

        // 查找同名进程(排除当前进程)
        foreach (Process process in Process.GetProcessesByName(processName))
        {
            if (process.Id == currentProcess.Id) continue;

            // 激活窗口
            if (process.MainWindowHandle != IntPtr.Zero)
            {
                SetForegroundWindow(process.MainWindowHandle);
                break;
            }
        }
    }
}

单实例场景 情况下,不需要显式调用 ReleaseMutex(),因为:

  • 持有锁的目的 是阻止其他实例运行,而非临时保护共享资源。
  • 程序退出时,操作系统会自动释放所有内核对象(包括 Mutex),其他实例可以重新获取。
  • 在单实例场景中,主线程创建Mutex时,initiallyOwned参数设置为true,意味着主线程立即拥有该Mutex

代码详解

1. Mutex 创建与检查

	using (Mutex mutex = new Mutex(
	    initiallyOwned: true,
	    name: "Global\\MySingleInstanceConsoleApp",
	    createdNew: out isNewInstance))
  • initiallyOwned: true:创建时立即获取所有权。
  • Global\\ 前缀:确保跨用户会话的全局唯一性(需管理员权限)。
  • createdNew 参数:判断是否成功创建新实例。
  • using 块的自动释放:
    • Mutex 实现了 IDisposable,using 块结束时调用 Dispose()。
    • Dispose() 会关闭内核句柄,但 不会自动调用 ReleaseMutex()。
    • 然而,在单实例场景中,程序退出后操作系统会强制释放锁,因此无需手动释放。

2. 多实例处理逻辑

if (!isNewInstance)
{
    Console.WriteLine("程序已在运行!正在激活已有窗口...");
    ActivateExistingWindow();
    Thread.Sleep(2000);
    return;
}
  • 发现已有实例时,提示用户并激活已有窗口。
    • 当第一次运行是实例时, isNewInstance 为ture 表示 mutex 是一个新的实例
    • 当再次或多次运行实例时, 通过指定的 name Global\\MySingleInstanceConsoleApp去创建实例时发现系统中已存在该名称的mutex 实例,因此 isNewInstance 返回 false 表示 mutex 已经存在创建的实例
  • 2秒后自动退出当前实例。

3. 窗口激活功能

[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);

private static void ActivateExistingWindow()
{
    // 查找同名进程并激活其主窗口
}
  • 使用 Windows API SetForegroundWindow 将已有实例窗口置顶。

  • 遍历同名进程,排除自身进程后激活第一个找到的窗口。

4. 异常处理

  • UnauthorizedAccessException:处理权限不足问题(如未以管理员身份运行)。
  • AbandonedMutexException:处理前一个实例未正常释放锁的情况。

5. 主程序逻辑

Console.Title = "单实例示例程序 (PID: " + Process.GetCurrentProcess().Id + ")";
Console.WriteLine("程序已启动,输入 'exit' 退出。");

// 模拟工作循环...
  • 显示当前进程ID便于调试。
  • 实现简单的控制台命令循环。

运行效果

  1. 首次运行:
程序已启动,输入 'exit' 退出。
> hello
处理命令: hello
> exit
  1. 二次运行
程序已在运行!正在激活已有窗口...
(2秒后自动退出,并激活第一个实例窗口)

注意事项

  1. 管理员权限:

    • 使用 Global\\ 前缀需以管理员身份运行程序,否则会触发 UnauthorizedAccessException。
    • 可在项目属性中勾选【启用 ClickOnce 安全设置】→【以管理员身份运行】。
  2. 跨平台限制:

    • SetForegroundWindow 仅适用于 Windows。
    • Linux/macOS 需使用其他方式实现窗口激活(如进程通信)。
  3. Mutex 名称唯一性:

    • 建议使用 GUID 或唯一标识符作为名称(如 Global\\{APP-GUID})。
  4. 防篡改保护:

    • 恶意程序可通过同名 Mutex 干扰你的应用,如需更高安全性,需结合其他机制(如加密令牌)。

2. 示例:跨进程文件写入

命名Mutex可用于协调多个进程对共享资源的访问。

// 进程A
using (Mutex mutex = new Mutex(false, "Global\\MySharedMutex"))
{
    if (mutex.WaitOne(5000))
    {
        File.AppendAllText("shared.txt", "进程A写入内容\n");
        mutex.ReleaseMutex();
    }
}

// 进程B
using (Mutex mutex = new Mutex(false, "Global\\MySharedMutex"))
{
    if (mutex.WaitOne(5000))
    {
        File.AppendAllText("shared.txt", "进程B写入内容\n");
        mutex.ReleaseMutex();
    }
}
  • 全局命名空间:Global\\前缀表示系统级Mutex(Windows特有),避免用户会话隔离问题。

  • 安全性:确保进程有权限访问同一命名Mutex。

3. 示例:控制硬件访问

协调多个进程使用同一硬件设备(如串口):

Mutex serialPortMutex = new Mutex(false, "Global\\SerialPortCOM1");
if (serialPortMutex.WaitOne(1000))
{
    using (var port = new SerialPort("COM1"))
    {
        port.Open();
        // 发送数据...
    }
    serialPortMutex.ReleaseMutex();
}

4. 示例:分布式锁原型

(需谨慎设计,通常建议使用专用分布式锁服务)

// 通过共享网络位置的文件模拟分布式锁
string lockFilePath = @"\\server\shared\lock.file";
Mutex distributedMutex = new Mutex(false, "Global\\DistributedFileLock");

if (distributedMutex.WaitOne())
{
    if (!File.Exists(lockFilePath))
    {
        File.Create(lockFilePath).Close();
        // 执行独占操作...
        File.Delete(lockFilePath);
    }
    distributedMutex.ReleaseMutex();
}

五、最佳实践

1. 使用 using 语句

为了确保互斥量资源能够正确释放,建议使用 using 语句来管理 Mutex 对象的生命周期。

using (var mutex = new Mutex())
{
    mutex.WaitOne();
    try
    {
        // 临界区代码
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}

2. 处理异常

确保在捕获异常时也释放互斥量,避免死锁。

try
{
    mutex.WaitOne();
    // 临界区代码
}
catch (Exception ex)
{
    Console.WriteLine($"An error occurred: {ex.Message}");
}
finally
{
    mutex.ReleaseMutex();
}

3. 避免死锁

确保每个获取互斥量的线程或进程最终都会释放互斥量,尤其是在复杂的应用场景中。

4. 使用命名互斥量时注意安全性

命名互斥量是全局可见的,因此要注意潜在的安全性问题。确保命名互斥量的名称具有足够的唯一性,并考虑使用安全描述符来限制访问权限。

5. 最佳实战完整示例

使用 Mutex 控制多进程对共享文件的访问

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

class Program
{
    // 共享文件名
    private const string LogFile = "shared_log.txt";
    // 全局 Mutex 名称(跨进程唯一标识)
    private const string MutexName = "Global\\MyGlobalFileMutex";

    static void Main(string[] args)
    {
        Console.Title = $"文件写入进程 (PID: {Process.GetCurrentProcess().Id})";
        Console.WriteLine("=== 跨进程安全文件写入演示 ===");
        Console.WriteLine("输入要模拟的写入线程数量(建议3-5):");
        
        if (!int.TryParse(Console.ReadLine(), out int threadCount) || threadCount < 1)
        {
            Console.WriteLine("输入无效,默认使用3个线程");
            threadCount = 3;
        }

        // 初始化共享文件(清空内容)
        File.WriteAllText(LogFile, $"日志初始化时间: {DateTime.Now:HH:mm:ss.fff}\n\n");

        // 启动多个模拟线程(每个线程代表一个独立操作)
        for (int i = 0; i < threadCount; i++)
        {
            int threadId = i + 1;
            new Thread(() => WriteToFile(threadId)).Start();
            Thread.Sleep(100); // 错开线程启动时间
        }

        Console.WriteLine("\n按任意键退出...");
        Console.ReadKey();
    }

    static void WriteToFile(int threadId)
    {
        const int maxRetry = 2;
        int attempt = 0;

        while (attempt <= maxRetry)
        {
            try
            {
                using (Mutex mutex = new Mutex(false, MutexName))
                {
                    Console.WriteLine($"[线程{threadId}] 尝试获取文件锁... (尝试 {attempt + 1}/3)");

                    // 尝试获取锁(最多等待3秒)
                    if (mutex.WaitOne(TimeSpan.FromSeconds(3)))
                    {
                        try
                        {
                            // 模拟复杂操作
                            var content = $"进程 {Process.GetCurrentProcess().Id} - 线程 {threadId} " +
                                         $"于 {DateTime.Now:HH:mm:ss.fff} 写入\n";
                            
                            File.AppendAllText(LogFile, content);
                            Console.WriteLine($"[线程{threadId}] 成功写入文件!");

                            // 模拟处理耗时
                            Thread.Sleep(new Random().Next(500, 1500));
                            return; // 成功完成
                        }
                        finally
                        {
                            mutex.ReleaseMutex();
                            Console.WriteLine($"[线程{threadId}] 已释放文件锁");
                        }
                    }
                    else
                    {
                        Console.WriteLine($"[线程{threadId}] 获取锁超时!");
                        attempt++;
                    }
                }
            }
            catch (AbandonedMutexException)
            {
                Console.WriteLine($"[线程{threadId}] 警告:检测到被遗弃的锁,自动回收!");
            }
            catch (UnauthorizedAccessException)
            {
                Console.WriteLine($"[线程{threadId}] 错误:需要管理员权限!");
                break;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[线程{threadId}] 未知错误:{ex.Message}");
                break;
            }
        }

        Console.WriteLine($"[线程{threadId}] 终止写入操作");
    }
}

代码详解:

1. 初始化设置

// 共享文件名
private const string LogFile = "shared_log.txt";
// 全局 Mutex 名称
private const string MutexName = "Global\\MyGlobalFileMutex";
  • 使用 Global\\ 前缀确保跨进程可见(Windows 系统需管理员权限)
  • 日志文件 shared_log.txt 会被多个线程/进程追加内容

2. 主逻辑流程

// 用户输入线程数 -> 初始化文件 -> 启动多个写入线程
new Thread(() => WriteToFile(threadId)).Start();
  • 每个线程模拟独立进程/线程的写入操作
  • 线程间启动间隔 100ms 模拟真实场景的随机性

3. 核心写入方法

using (Mutex mutex = new Mutex(false, MutexName))
{
    if (mutex.WaitOne(TimeSpan.FromSeconds(3)))
    {
        try
        {
            // 安全写入文件
            File.AppendAllText(...);
        }
        finally
        {
            mutex.ReleaseMutex();
        }
    }
}
  • using 块确保 Mutex 资源释放
  • WaitOne(3000) 设置 3 秒超时避免无限阻塞
  • finally 确保锁释放

4. 异常处理

catch (AbandonedMutexException)  // 处理前一个持有者异常退出的情况
catch (UnauthorizedAccessException) // 权限不足
catch (Exception ex) // 其他未知错误
  • 明确处理常见异常类型
  • 提供重试机制(最多尝试 3 次)

运行效果演示

场景1:单进程多线程

=== 跨进程安全文件写入演示 ===
输入要模拟的写入线程数量(建议3-5):
3

[线程1] 尝试获取文件锁... (尝试 1/3)
[线程2] 尝试获取文件锁... (尝试 1/3)
[线程3] 尝试获取文件锁... (尝试 1/3)
[线程1] 成功写入文件!
[线程1] 已释放文件锁
[线程3] 成功写入文件!
[线程3] 已释放文件锁
[线程2] 成功写入文件!
[线程2] 已释放文件锁

生成文件内容 (shared_log.txt):

日志初始化时间: 14:25:36.112

进程 1234 - 线程 114:25:36.345 写入
进程 1234 - 线程 314:25:36.901 写入
进程 1234 - 线程 214:25:37.412 写入

场景2:多进程竞争

同时运行两个实例:

实例1输出:

[线程1] 成功写入文件!
[线程1] 已释放文件锁
[线程2] 获取锁超时!
[线程2] 尝试获取文件锁... (尝试 2/3)

实例2输出:

[线程1] 获取锁超时!
[线程1] 尝试获取文件锁... (尝试 2/3)
[线程1] 成功写入文件!
[线程1] 已释放文件锁

生成文件内容无交错,体现互斥效果。


关键注意事项

  1. 管理员权限需求:
  • 在 Windows 上使用 Global\\ 前缀时,需以管理员身份运行程序
  • 解决方法:在项目属性中勾选【启用 ClickOnce 安全设置】→【以管理员身份运行】
  1. 跨平台行为:
// Linux/macOS 的 Mutex 名称会映射到文件系统路径
// private const string MutexName = "/tmp/MyGlobalFileMutex";
  1. 文件路径权限:
  • 确保所有进程对 shared_log.txt 有写入权限
  • 建议使用绝对路径(如 C:\logs\shared.log)
  1. 性能优化
// 高频写入场景可优化为批量写入
var logs = new List<string>();
logs.Add("内容1");
logs.Add("内容2");
File.AppendAllLines(LogFile, logs);
  1. 扩展实践建议
  • 添加日志滚动:
// 每天创建新文件
string LogFile = $"log_{DateTime.Today:yyyyMMdd}.txt";
  • 实现优先级写入
if (mutex.WaitOne(0)) // 非阻塞尝试
{
    // 紧急日志立即写入
}
  • 结合 FileStream 控制缓存:
using (var fs = new FileStream(LogFile, FileMode.Append, FileAccess.Write, FileShare.Read))
using (var writer = new StreamWriter(fs))
{
    writer.AutoFlush = true; // 实时写入
    writer.WriteLine(content);
}

六、与Monitor的区别

特性MutexMonitor
作用范围跨进程同一进程内
锁粒度较粗(系统级资源)较细(轻量级对象锁)
性能开销高(涉及内核切换)低(用户模式实现)
等待机制支持超时、跨进程等待仅支持同一进程内的条件等待
异常处理需处理AbandonedMutexException无类似异常

七、initiallyOwned参数的使用

1. initiallyOwned: true 的适用场景

当 创建线程需要立即持有锁,且希望 直接控制资源访问权 时使用。常见场景如下:

(1) 单实例应用程序

bool isNewInstance;
using (Mutex mutex = new Mutex(
    initiallyOwned: true, 
    name: "Global\\MyAppMutex", 
    createdNew: out isNewInstance))
{
    if (!isNewInstance)
    {
        Console.WriteLine("程序已在运行!");
        Environment.Exit(0);
    }
    // 主程序逻辑...
}
  • 作用:程序启动时立即尝试获取锁,若 isNewInstance 为 false,说明其他实例正在运行。
  • 关键点:锁会一直持有到程序退出,无需手动释放(操作系统自动回收)。

(2) 初始化独占资源

// 创建锁并立即获取所有权
Mutex mutex = new Mutex(true, "Global\\SharedResourceMutex");
try
{
    if (mutex.WaitOne(0)) // 确认已获得所有权
    {
        InitializeExclusiveResource(); // 初始化需要独占访问的资源
    }
}
finally
{
    mutex.ReleaseMutex(); // 初始化完成后释放,允许其他线程访问
}
  • 作用:确保资源的初始化过程不被其他线程干扰。

2. initiallyOwned: false 的适用场景

当 创建线程不需要立即持有锁,而是希望后续通过 WaitOne 动态获取时使用。常见场景如下:

(1) 跨进程共享资源的动态控制

Mutex mutex = new Mutex(false, "Global\\SharedFileMutex");
try
{
    if (mutex.WaitOne(5000)) // 等待锁的获取
    {
        File.AppendAllText("data.log", "写入内容...");
    }
    else
    {
        Console.WriteLine("获取锁超时!");
    }
}
finally
{
    mutex.ReleaseMutex(); // 必须显式释放
}
  • 作用:允许多个进程/线程按需竞争锁,灵活控制资源访问。

(2) 锁的延迟获取

// 创建锁但不立即持有
Mutex mutex = new Mutex(false, "Global\\DelayedLock");
// ...其他初始化操作...

// 在需要时再获取锁
if (mutex.WaitOne(1000))
{
    try
    {
        // 操作共享资源
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}
  • 作用:推迟锁的获取时机,减少不必要的锁持有时间。

3. 核心原则总结

参数值适用场景注意事项
initiallyOwned: true1. 单实例应用启动时立即锁定
2. 需要独占初始化资源
3. 明确要求创建即拥有锁
必须检查 createdNew 参数,确保锁实际被获取。
若未获取成功,当前线程不持有锁。
initiallyOwned: false1. 按需动态获取锁
2. 跨进程协作控制
3. 避免初始化时立即阻塞
必须后续调用 WaitOne 获取锁,
并严格通过 try-finally 确保释放。

4. 常见误区与验证

误区1:initiallyOwned: true 一定能获取锁

// 错误示例:忽略 createdNew 参数
Mutex mutex = new Mutex(true, "Global\\MyMutex");
// 如果其他实例已持有该锁,此处 createdNew 为 false,但构造函数不会抛出异常!
  • 修正:必须检查 createdNew 参数:
bool isNewInstance;
var mutex = new Mutex(true, "Global\\MyMutex", out isNewInstance);
if (!isNewInstance)
{
    Console.WriteLine("锁已被其他线程/进程持有!");
}

误区2:不释放 initiallyOwned: true 的锁

// 错误示例:单实例应用中不释放锁(但实际是安全的)
using (Mutex mutex = new Mutex(true, "Global\\MyAppMutex", out isNewInstance))
{
    // 程序运行期间一直持有锁
}
// 程序退出时操作系统自动释放,无需手动 ReleaseMutex()

结论:单实例场景中无需手动释放,但动态锁控制场景必须显式释放。

5. 选择参数的决策流程

  1. 是否需要立即阻止其他实例/线程访问资源?
    • 是 → initiallyOwned: true(如单实例应用)。
    • 否 → initiallyOwned: false(如动态资源控制)。
  2. 锁的持有时间是否与程序生命周期一致?
    • 是 → initiallyOwned: true,依赖系统自动释放。
    • 否 → initiallyOwned: false,手动控制锁的获取/释放。
  3. 是否需要跨进程协作?
    • 是 → 优先使用 initiallyOwned: false,按需竞争锁。
    • 否 → 根据场景灵活选择。

6. 验证代码示例

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        // 场景1:单实例应用(initiallyOwned: true)
        bool isNewInstance;
        using (Mutex mutex = new Mutex(true, "Global\\TestMutex", out isNewInstance))
        {
            if (!isNewInstance)
            {
                Console.WriteLine("已有实例运行!");
                return;
            }
            Console.WriteLine("首次启动,持有锁...");
            Console.ReadKey();
        }

        // 场景2:动态锁控制(initiallyOwned: false)
        using (Mutex dynamicMutex = new Mutex(false, "Global\\DynamicMutex"))
        {
            if (dynamicMutex.WaitOne(2000))
            {
                try
                {
                    Console.WriteLine("动态获取锁成功!");
                }
                finally
                {
                    dynamicMutex.ReleaseMutex();
                }
            }
        }
    }
}

7. 小结

  • initiallyOwned: true:适用于 立即锁定 且 持有锁至程序退出 的场景(如单实例应用)。
  • initiallyOwned: false:适用于 按需获取锁 的场景(如跨进程资源共享)。
  • 关键区别在于 锁的初始化所有权归属,需结合 createdNew 参数和实际业务逻辑判断。

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
Mutexes
Mutex 锁


http://www.niftyadmin.cn/n/5845263.html

相关文章

MES系统对于中小型制造企业有什么价值?

由于近几年来我国制造业的飞速发展&#xff0c;催生出了很多为大企业代工或细分制造领域的中小型企业。这些中小型企业通常在初期因生产规模和资本的积累十分有限&#xff0c;所以在信息化投入方面较少。随着企业的发展需求&#xff0c;为应对多品种、小批量的制造模式在生产、…

1.1 画质算法的主要任务

文章目录 画质算法及分类画质问题的核心&#xff1a;退化 画质算法及分类 图像画质算法是指&#xff0c;处理图像或视频数字信号&#xff0c;以提高其视觉质量、人眼感官的算法。图像画质算法可分为&#xff1a;去噪&#xff08;Denoising), 超分辨率&#xff08;Super-Resolut…

消息队列高手课总结笔记——基础篇速通

第一章&#xff0c;整体认识 一&#xff0c;解决的问题&#xff08;使用场景&#xff09; 主要&#xff1a; 1&#xff0c;异步处理 2&#xff0c;流量控制 3&#xff0c;服务解耦 补充&#xff1a; 1&#xff0c;作为发布/订阅系统实现一个微服务级系统间的观察者模式&#…

202412 青少年软件编程等级考试C/C++ 二级真题答案及解析

第 1 题 逆行 题目描述 网上有个段子说&#xff1a;妻子在家听广播&#xff0c;听到某高速路上有一辆车在逆行&#xff0c;想到丈夫在那条高速上行驶&#xff0c;就打电话对丈夫说&#xff1a;“老公啊&#xff0c;你走的那条高速上有一辆车在逆行&#xff0c;你小心点。”她丈…

java手动实现常见数据结构

在 Java 中&#xff0c;常用的数据结构可以通过 集合框架&#xff08;Collections Framework&#xff09; 实现&#xff0c;也可以手动实现。以下是常见数据结构及其特点&#xff0c;以及对应的 Java 实现示例&#xff1a; 1. 数组&#xff08;Array&#xff09; 特点&#xf…

Maven 依赖管理全面解析

目录 1. Maven 简介 2. 依赖的基本概念 依赖坐标 依赖范围 3. Maven 仓库 本地仓库 远程仓库 4. 依赖传递 5. 依赖冲突 6. 在 pom.xml 文件中管理依赖 基本依赖配置 依赖范围配置 依赖排除配置 依赖管理配置&#xff08;&#xff09; 1. Maven 简介 Maven 是一个…

CTF SQL注入学习笔记

部分内容来自于SQL注入由简入精_哔哩哔哩_bilibili SQL语句 1.mysqli_error()&#xff1a;返回最近调用函数的最后一个错误描述 语法&#xff1a;mysqli_error(connection) 规定要使用的Mysql连接; 返回一个带有错误描述的字符串。如果没有错误发生则返回 "" 2…

【自学笔记】Deepseek的基础知识点总览-持续更新

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Deepseek知识点总览一、Deepseek简介二、Deepseek的三大适用模式1. 基础模型&#xff08;V3&#xff09;2. 深度思考&#xff08;R1&#xff09;3. 联网搜索模式 三…