如何在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
相关产品推荐
相关产品推荐

