You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#多线程应用中创建锁池以抢占式获取单个非线程安全资源?

这是个非常实用的多线程场景!要实现你想要的lockAny功能,核心思路就是尝试获取集合中任意一个锁的所有权,拿到锁后再安全地操作对应的资源。下面我给你两种基于C#原生机制的可行实现,都是生产环境中可以直接用的:

方案一:基于Monitor.TryEnter的轻量级轮询实现

Monitor是C#中最常用的轻量级线程同步工具,性能好且使用简单。我们可以封装一个方法,逐个尝试获取锁,直到拿到其中一个为止:

using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;

public static class LockHelper
{
    /// <summary>
    /// 尝试获取任意一个锁,并返回对应的资源
    /// </summary>
    /// <param name="lockResourcePairs">锁对象与资源的键值对集合</param>
    /// <param name="acquiredResource">成功获取锁后对应的资源</param>
    /// <param name="timeout">超时时间(毫秒),默认无限等待</param>
    /// <returns>是否成功获取锁</returns>
    public static bool TryLockAny<T>(IEnumerable<KeyValuePair<object, T>> lockResourcePairs, out T acquiredResource, int timeout = Timeout.Infinite)
    {
        acquiredResource = default;
        var stopwatch = Stopwatch.StartNew();

        foreach (var pair in lockResourcePairs)
        {
            // 计算剩余超时时间,避免累计超时
            int remainingTimeout = timeout - (int)stopwatch.ElapsedMilliseconds;
            if (remainingTimeout <= 0)
                break;

            // 尝试获取锁
            if (Monitor.TryEnter(pair.Key, remainingTimeout))
            {
                try
                {
                    acquiredResource = pair.Value;
                    return true;
                }
                catch
                {
                    // 若后续初始化出错,必须释放锁
                    Monitor.Exit(pair.Key);
                    throw;
                }
            }
        }

        return false;
    }
}

使用示例

// 定义全局锁和对应的非线程安全资源(必须是static/全局,避免被GC回收)
private static readonly object _lockObj1 = new object();
private static readonly MyResource _resource1 = new MyResource("资源A");

private static readonly object _lockObj2 = new object();
private static readonly MyResource _resource2 = new MyResource("资源B");

private static readonly object _lockObj3 = new object();
private static readonly MyResource _resource3 = new MyResource("资源C");

public void UseAnyResourceSafely()
{
    // 构建锁与资源的映射集合
    var lockResourceMap = new List<KeyValuePair<object, MyResource>>
    {
        new(_lockObj1, _resource1),
        new(_lockObj2, _resource2),
        new(_lockObj3, _resource3)
    };

    // 尝试获取任意一个锁,超时5秒
    if (LockHelper.TryLockAny(lockResourceMap, out var targetResource, 5000))
    {
        try
        {
            // 安全执行非线程操作
            targetResource.DoSomething();
        }
        finally
        {
            // 必须在finally中释放锁,避免异常导致锁泄漏
            var lockToRelease = lockResourceMap.First(p => p.Value == targetResource).Key;
            Monitor.Exit(lockToRelease);
        }
    }
    else
    {
        // 处理超时逻辑,比如日志告警或抛出异常
        throw new TimeoutException("未能在指定时间内获取任何资源的锁");
    }
}

// 自定义非线程安全资源类
public class MyResource
{
    public string Name { get; }

    public MyResource(string name) => Name = name;

    public void DoSomething()
    {
        Console.WriteLine($"线程[{Thread.CurrentThread.ManagedThreadId}]正在操作{Name}");
        // 模拟非线程安全的业务逻辑
        Thread.Sleep(1000);
    }
}
方案二:基于WaitHandle.WaitAny的批量等待实现

如果你的锁数量较多,轮询的效率会降低,这时候可以用WaitHandle.WaitAny来一次性等待所有锁的信号。这个方案适合需要跨进程同步的场景(比如用Mutex),也支持线程内同步:

using System.Collections.Generic;
using System.Threading;

public static class LockHelper
{
    /// <summary>
    /// 等待任意一个Mutex信号,并返回对应的资源
    /// </summary>
    public static bool WaitAnyMutex<T>(IEnumerable<KeyValuePair<Mutex, T>> mutexResourcePairs, out T acquiredResource, int timeout = Timeout.Infinite)
    {
        var mutexArray = mutexResourcePairs.Select(p => p.Key).ToArray();
        var resourceArray = mutexResourcePairs.Select(p => p.Value).ToArray();

        // 等待任意一个Mutex变为可用
        int waitIndex = WaitHandle.WaitAny(mutexArray, timeout);

        if (waitIndex == WaitHandle.WaitTimeout)
        {
            acquiredResource = default;
            return false;
        }

        acquiredResource = resourceArray[waitIndex];
        return true;
    }
}

使用示例

// 定义全局Mutex(支持跨进程同步)
private static readonly Mutex _mutex1 = new Mutex();
private static readonly MyResource _resource1 = new MyResource("跨进程资源A");

private static readonly Mutex _mutex2 = new Mutex();
private static readonly MyResource _resource2 = new MyResource("跨进程资源B");

public void UseAnyCrossProcessResource()
{
    var mutexResourceMap = new List<KeyValuePair<Mutex, MyResource>>
    {
        new(_mutex1, _resource1),
        new(_mutex2, _resource2)
    };

    if (LockHelper.WaitAnyMutex(mutexResourceMap, out var targetResource, 5000))
    {
        try
        {
            targetResource.DoSomething();
        }
        finally
        {
            // 释放对应的Mutex
            var mutexToRelease = mutexResourceMap.First(p => p.Value == targetResource).Key;
            mutexToRelease.ReleaseMutex();
        }
    }
    else
    {
        throw new TimeoutException("未能获取任何跨进程资源锁");
    }
}
关键注意事项
  • 锁对象必须全局/static:如果锁是局部变量,每个线程会创建自己的锁实例,完全起不到同步作用。
  • 务必在finally中释放锁:不管操作成功还是抛出异常,都要确保锁被释放,否则会导致死锁或资源泄漏。
  • 提前缓存锁与资源的映射:如果映射固定,建议提前创建一个Dictionary<object, T>,避免每次调用都用First查找锁对象,提升性能。
  • 选择合适的锁类型:仅线程内同步用Monitor(轻量高效),跨进程同步用Mutex(稍重但支持跨进程)。

内容的提问来源于stack exchange,提问作者sfphoton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:22:30