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

使用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);
    }
}

关键注意事项

  1. 信号量的释放必须放在finally块:不管业务逻辑是否抛出异常,都要确保信号量被释放,否则会导致后续线程永远无法获取资源,造成死锁。
  2. 锁的范围要尽可能小:我们只在实例创建时加锁,业务逻辑执行时没有锁,这样4个实例可以同时处理任务,不会影响并发效率。
  3. 实例的独立性:确保每个线程持有的实例是独立的,不要在实例之间共享状态,否则会出现线程安全问题。
  4. 异步支持:如果你的系统用异步编程,SemaphoreSlim的WaitAsync()比同步的Wait()更友好,不会阻塞线程池线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:09:11