使用Semaphore时4个线程为何出现锁死问题?
解决方案:用SemaphoreSlim+锁控制多线程下的实例并发数
嘿,这个场景我太熟了!你的思路完全找对了——用Semaphore控制并发实例数,再加锁避免实例创建时的竞态问题,刚好能完美满足你的需求。我给你拆解下具体实现细节和完整代码:
核心思路拆解
- 用SemaphoreSlim控制并发数:选择
SemaphoreSlim而非传统的Semaphore,因为它更轻量,还支持异步等待,适合现代多线程场景。我们把它的初始计数和最大计数都设为4,确保同一时间最多有4个线程能创建并持有目标类实例。 - 锁保护实例创建逻辑:用静态锁对象包裹实例初始化代码,防止多个线程同时触发实例创建(虽然Semaphore限制了并发数,但创建瞬间仍可能出现竞态)。这里的锁只会影响实例创建的瞬间,不会阻塞业务逻辑的并发执行,所以完全不用担心会变成单实例独占的情况。
- 严格管理实例生命周期:线程启动时先获取信号量,再创建实例;线程处理完成(或等待5-60秒后)销毁实例,最后必须释放信号量,确保后续线程能正常获取资源。
完整代码示例
using System; using System.Threading; using System.Threading.Tasks; // 你的目标业务类,这里模拟业务逻辑 public class MyLimitedResource { public void ProcessTask() { Console.WriteLine($"实例 {GetHashCode()} 正在执行任务,线程ID: {Thread.CurrentThread.ManagedThreadId}"); } } // 实例管理器,负责控制并发实例数 public class InstanceController { // 限制最多4个并发实例 private static readonly SemaphoreSlim _concurrencySemaphore = new SemaphoreSlim(4, 4); // 防止实例创建时的竞态条件 private static readonly object _instanceCreationLock = new object(); public async Task ExecuteThreadTask() { try { // 等待获取信号量,确保不超过4个并发实例 await _concurrencySemaphore.WaitAsync(); MyLimitedResource instance = null; lock (_instanceCreationLock) { // 确保同一时间只有一个线程在创建实例,避免重复初始化的竞态 instance = new MyLimitedResource(); Console.WriteLine($"已创建实例 {instance.GetHashCode()},当前活跃实例数: {4 - _concurrencySemaphore.CurrentCount}"); } // 执行业务逻辑 instance.ProcessTask(); // 模拟实例5-60秒后释放(实际场景可替换为业务释放条件) var releaseDelayMs = new Random().Next(5000, 60000); await Task.Delay(releaseDelayMs); // 销毁实例(如果有非托管资源,这里要添加清理逻辑) Console.WriteLine($"已销毁实例 {instance.GetHashCode()},等待了 {releaseDelayMs / 1000} 秒"); instance = null; } finally { // 必须释放信号量,无论业务是否出错,避免死锁 _concurrencySemaphore.Release(); Console.WriteLine($"信号量已释放,当前可用名额: {_concurrencySemaphore.CurrentCount}"); } } } // 使用示例 class Program { static async Task Main(string[] args) { var controller = new InstanceController(); // 模拟20个线程同时启动的场景 var taskList = new Task[20]; for (int i = 0; i < 20; i++) { taskList[i] = controller.ExecuteThreadTask(); // 模拟线程陆续启动,更贴近真实场景 await Task.Delay(100); } await Task.WhenAll(taskList); } }
关键注意事项
- 信号量的释放必须放在finally块:不管业务逻辑是否抛出异常,都要确保信号量被释放,否则会导致后续线程永远无法获取资源,造成死锁。
- 锁的范围要尽可能小:我们只在实例创建时加锁,业务逻辑执行时没有锁,这样4个实例可以同时处理任务,不会影响并发效率。
- 实例的独立性:确保每个线程持有的实例是独立的,不要在实例之间共享状态,否则会出现线程安全问题。
- 异步支持:如果你的系统用异步编程,
SemaphoreSlim的WaitAsync()比同步的Wait()更友好,不会阻塞线程池线程。
内容的提问来源于stack exchange,提问作者Matt W
相关产品推荐
相关产品推荐

