C# Semaphore与SemaphoreSlim限制多线程资源池访问实战

栗子
栗子
发布时间:2026-10-13| 4人浏览过
原创

在C#多线程开发中,限制同时访问某一资源的线程数量是常见的并发控制需求。System.Threading.Semaphore和System.Threading.SemaphoreSlim是两个核心类,它们通过信号量机制管理资源池的并发访问量。

Semaphore 类的构造函数与常用 API

Semaphore 是一个内核级对象(Kernel-time),支持跨进程同步,适用于需要严格控制且可能涉及多进程的场景。其使用相对较重,但功能强大。

构造函数

  • Semaphore(Int32, Int32): 初始化实例,指定初始入口数和最大并发入口数。
  • Semaphore(Int32, Int32, String): 上述基础上,指定系统信号灯对象的名称,可用于跨进程同步。
  • Semaphore(Int32, Int32, String, Boolean): 进一步指定一个布尔变量,接收是否创建了新系统信号量的指示。

常用方法

在使用 Semaphore 时,主要依赖以下方法进行状态控制和资源释放:

  • WaitOne() / WaitOne(Int32) / WaitOne(TimeSpan): 阻止当前线程,直到获得进入信号量的权限。可指定超时时间。
  • Release() / Release(Int32): 退出信号量,并可选择性地增加计数(即增加可用资源数)。
  • OpenExisting(String) / TryOpenExisting(String, Semaphore): 打开指定名称的系统信号量(如果存在)。
  • Close(): 释放由当前 WaitHandle 占用的所有资源。使用后建议调用此方法以释放内核对象。

使用 Semaphore 限制资源池示例

下面通过一个具体案例演示:启动10个线程执行计算任务,但同一时间只允许最多3个线程进入资源池执行。

第1步:实例化 Semaphore 并设置权限

首先创建一个 Semaphore 对象。构造函数第一个参数为initialCount(初始可用数),第二个为maximumCount(最大并发数)。

在以下示例中,我们将初始值设为0,意味着初始状态下没有任何线程可以进入。随后通过 _pool.Release(3) 手动放通3个名额,模拟“按钮开启”的效果。

class Program
{
    private static int sum = 0;
    private static Semaphore _pool;
    private static int isComplete = 0;

    static void Main(string[] args)
    {
        Console.WriteLine("执行程序");

        // 初始允许0个进入,最大允许3个同时进入
        _pool = new Semaphore(0, 3);

        for (int i = 0; i < 10; i++)
        {
            Thread thread = new Thread(new ParameterizedThreadStart(AddOne));
            thread.Start(i + 1);
        }

        Console.WriteLine("按任意键放通资源池...");
        Console.ReadKey();

        // 手动放通3个名额
        _pool.Release(3);

        // 等待所有线程完成任务
        while (isComplete < 10)
        {
            Thread.Sleep(TimeSpan.FromSeconds(1));
        }

        Console.WriteLine($"最终求和结果: {sum}");
        _pool.Close(); // 释放内核资源
    }

    public static void AddOne(object n)
    {
        int id = (int)n;
        Console.WriteLine($"线程{id}启动,等待进入资源池...");
        
        // 阻塞直到获得进入权限
        _pool.WaitOne();
        Console.WriteLine($"线程{id}已进入资源池");

        try
        {
            // 模拟资源占用和操作
            for (int i = 0; i < 10; i++)
            {
                Interlocked.Add(ref sum, 1);
                Thread.Sleep(TimeSpan.FromMilliseconds(500));
            }
        }
        finally
        {
            // 必须释放,否则其他线程将永久阻塞
            _pool.Release();
            isComplete++;
            Console.WriteLine($"线程{id}退出资源池");
        }
    }
}

代码逻辑解析

  1. 等待获取权限: _pool.WaitOne() 调用后,如果资源池已满(计数为0),线程会被挂起。当有其他线程调用 Release() 释放资源时,等待队列中的线程将被唤醒并获得计数。
  2. 释放资源: _pool.Release() 将计数加1。注意,这里必须在业务逻辑结束后(通常在finally块中)调用,以防止因异常导致资源不释放而引发死锁。

SemaphoreSlim 轻量级替代方案

在大多数 .NET 应用开发中,我们只需要在同一进程内进行线程同步。SemaphoreSlim` 是 `Semaphore` 的轻量级替代者,专为高性能设计。

主要特点

  • 进程内同步: 不支持跨进程通信,因此性能优于 Semaphore。
  • 异步友好: 提供 WaitAsync()` 和 ReleaseAsync()` 方法,避免阻塞线程池线程,更适合 I/O 密集型或高并发 Web 场景。
  • 无需 Close: 它是托管类型,垃圾回收会自动处理内存释放,通常不需要显式调用关闭方法。

构造函数

  • SemaphoreSlim(Int32): 仅指定初始可用数(默认最大等于初始数)。
  • SemaphoreSlim(Int32, Int32): 指定初始可用数和最大并发数。

改造后的示例

将上述代码替换为 SemaphoreSlim,核心逻辑几乎不变,但 API 略有不同:

class Program
{
    private static int sum = 0;
    private static SemaphoreSlim _pool;
    private static int isComplete = 0;

    static async Task Main(string[] args)
    {
        Console.WriteLine("执行程序 (SemaphoreSlim)");

        // 初始0,最大3
        _pool = new SemaphoreSlim(0, 3);

        var tasks = new List<Task>();
        for (int i = 0; i < 10; i++)
        {
            tasks.Add(Task.Run(() => AddOne(i + 1)));
        }

        Console.WriteLine("按任意键放通资源池...");
        Console.ReadKey();

        // 放通3个名额
        _pool.Release(3);

        await Task.WhenAll(tasks);
        Console.WriteLine($"最终求和结果: {sum}");
    }

    public static async Task AddOne(int id)
    {
        Console.WriteLine($"线程{id}启动...");
        
        // 等待进入权限
        await _pool.WaitAsync();
        Console.WriteLine($"线程{id}已进入");

        try
        {
            for (int i = 0; i < 10; i++)
            {
                Interlocked.Add(ref sum, 1);
                await Task.Delay(500);
            }
        }
        finally
        {
            // 释放权限
            _pool.Release();
            Console.WriteLine($"线程{id}退出");
        }
    }
}

Semaphore 与 SemaphoreSlim 的核心区别

特性 Semaphore SemaphoreSlim
同步范围 支持跨进程(需命名) 仅限进程内
底层实现 操作系统内核对象 纯 .NET 托管代码(自旋锁等)
性能 较轻量级,但有上下文切换开销 更高效,适合高频调用
严格性监控 严格追踪 Enter/Exit 配对,错误调用会抛异常 不强制检查调用方标识,较宽松
异步支持 原生不支持,需配合 Task 包装 原生支持 WaitAsync / ReleaseAsync

注意事项

  1. Count 准确性: 对于 SemaphoreSlim,如果未正确成对调用 Wait/Release,CurrentCount 可能会变得不准确(甚至超过最大值)。编程人员需确保逻辑严密。
  2. 异常处理: 无论使用哪个类,释放资源的操作务必放在 finally 块中,或者使用 await using(如果有实现IDisposable的最佳实践,虽本例未展示Disposable模式的标准用法,但原则一致)。

总结

Semaphore 和 SemaphoreSlim 都是解决 C# 多线程资源竞争的有效手段。如果需要跨进程限制并发或需要极高的系统级隔离,选择 Semaphore;而在绝大多数常规的多线程和高并发应用开发中,推荐使用更轻量、支持异步操作的 SemaphoreSlim。

以上就是C# Semaphore与SemaphoreSlim限制多线程资源池访问的详细内容,更多关于C#并发编程的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。