两组操作同步方案咨询:组内共享、组间互斥的同步机制
这是个非常典型的双组互斥共享场景,刚好可以通过组合基础同步原语来实现,或者封装成自定义锁类来复用。下面我给你两种实用的实现思路,附代码示例:
核心逻辑梳理
我们需要的是:
- A组内任意数量操作可并发执行
- B组内任意数量操作可并发执行
- A、B组操作绝对互斥,同一时间只能有一组在运行
本质上这是把ReaderWriterLockSlim的"读"权限拆成了两个独立的共享组,组间互斥,组内共享。
方案一:自定义双组同步锁类(推荐复用)
这个方案封装成独立类,代码更整洁,复用性强。这里用C#的Monitor来实现条件等待(类似原生的条件变量):
public class GroupedMutex { private readonly object _syncLock = new object(); private int _activeGroup = 0; // 0=无活跃组,1=A组,2=B组 private int _aActiveCount = 0; private int _bActiveCount = 0; // 进入A组 public void EnterGroupA() { lock (_syncLock) { // 等待B组所有操作完全退出 while (_activeGroup == 2) { Monitor.Wait(_syncLock); } // 标记当前活跃组为A,增加A组计数 _activeGroup = 1; _aActiveCount++; } } // 退出A组 public void ExitGroupA() { lock (_syncLock) { if (--_aActiveCount == 0) { // A组最后一个操作退出,重置状态并通知等待的B组 _activeGroup = 0; Monitor.PulseAll(_syncLock); } } } // 进入B组 public void EnterGroupB() { lock (_syncLock) { // 等待A组所有操作完全退出 while (_activeGroup == 1) { Monitor.Wait(_syncLock); } _activeGroup = 2; _bActiveCount++; } } // 退出B组 public void ExitGroupB() { lock (_syncLock) { if (--_bActiveCount == 0) { _activeGroup = 0; Monitor.PulseAll(_syncLock); } } } // 异步版本(适配异步操作场景) public async Task EnterGroupAAsync() { // 用Task.Run包装同步锁,避免阻塞异步上下文 await Task.Run(EnterGroupA); } public async Task ExitGroupAAsync() { await Task.Run(ExitGroupA); } public async Task EnterGroupBAsync() { await Task.Run(EnterGroupB); } public async Task ExitGroupBAsync() { await Task.Run(ExitGroupB); } }
使用示例
var groupLock = new GroupedMutex(); // A组操作 void RunGroupATask() { groupLock.EnterGroupA(); try { Console.WriteLine($"A组操作执行中,线程ID: {Thread.CurrentThread.ManagedThreadId}"); // 模拟业务逻辑 Thread.Sleep(1500); } finally { groupLock.ExitGroupA(); Console.WriteLine($"A组操作完成,线程ID: {Thread.CurrentThread.ManagedThreadId}"); } } // B组操作 void RunGroupBTask() { groupLock.EnterGroupB(); try { Console.WriteLine($"B组操作执行中,线程ID: {Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(1000); } finally { groupLock.ExitGroupB(); Console.WriteLine($"B组操作完成,线程ID: {Thread.CurrentThread.ManagedThreadId}"); } } // 测试:启动3个A组线程,2个B组线程 for (int i = 0; i < 3; i++) { new Thread(RunGroupATask).Start(); } Thread.Sleep(500); // 让A组先启动 for (int i = 0; i < 2; i++) { new Thread(RunGroupBTask).Start(); }
方案二:轻量实现(无需自定义类)
如果只是临时场景,不想封装类,可以用SemaphoreSlim加全局锁快速实现:
// 全局同步变量 private readonly object _crossGroupMutex = new object(); private readonly SemaphoreSlim _aSemaphore = new SemaphoreSlim(int.MaxValue); private readonly SemaphoreSlim _bSemaphore = new SemaphoreSlim(int.MaxValue); private int _currentActiveGroup = 0; // 0=无,1=A,2=B // 执行A组操作的包装方法 async Task ExecuteGroupA(Func<Task> action) { await _aSemaphore.WaitAsync(); try { lock (_crossGroupMutex) { // 等待B组完全退出 while (_currentActiveGroup == 2) Monitor.Wait(_crossGroupMutex); _currentActiveGroup = 1; } // 执行A组业务逻辑 await action(); } finally { lock (_crossGroupMutex) { _aSemaphore.Release(); // 检查是否是最后一个A组操作,是的话重置状态并通知B组 if (_aSemaphore.CurrentCount == int.MaxValue) { _currentActiveGroup = 0; Monitor.PulseAll(_crossGroupMutex); } } } } // 执行B组操作的包装方法 async Task ExecuteGroupB(Func<Task> action) { await _bSemaphore.WaitAsync(); try { lock (_crossGroupMutex) { while (_currentActiveGroup == 1) Monitor.Wait(_crossGroupMutex); _currentActiveGroup = 2; } await action(); } finally { lock (_crossGroupMutex) { _bSemaphore.Release(); if (_bSemaphore.CurrentCount == int.MaxValue) { _currentActiveGroup = 0; Monitor.PulseAll(_crossGroupMutex); } } } }
关键注意事项
- 无论哪种方案,必须在
finally块中释放锁/信号量,避免因异常导致死锁 - 异步场景优先使用带
Async的方法,避免阻塞线程池 - 如果需要限制组内并发数(比如最多5个A组操作同时执行),只需要把
SemaphoreSlim的初始计数改成对应数字即可
内容的提问来源于stack exchange,提问作者ygoe
相关产品推荐
相关产品推荐

