C# Semaphore 信号量用法详解与示例
Semaphore(信号量)在C#中是一个强大的同步原语,用于控制同时访问某个资源或代码段的线程数量。它将信号视为“许可证”,只有拥有许可证的线程才能执行,否则将被阻塞。本文将详细讲解其核心原理并提供完整的代码示例。
结果与目标
最终目标是实现一个基于 `System.Threading.Semaphore` 的线程安全机制。通过这个示例,你可以看到如何:
- 初始化授权池:设置允许的最大并发数和初始可用数。
- 申请与释放:使用
WaitOne获取许可,Release归还许可。 - 处理竞争:观察多个线程争抢单一许可时的行为,包括等待、超时和并发限制。
准备条件
为了实现上述功能,你需要了解以下核心概念:
- Semaphore构造器:接收两个关键参数:
initialCount(当前可用许可数)和maxCount(最大许可容量)。 WaitOne(int millisecondsTimeout):尝试获取许可。如果指定了超时时间且在此期间无法获得许可,则返回 false 并释放当前等待状态。Release():增加可用的许可计数。如果超出最大值将抛出InvalidOperationException。
真实操作顺序与代码解析
第1步:定义信号量与线程类
首先,我们需要在一个静态上下文中声明信号量,确保所有实例共享同一组权限池。在这里,我们创建一个最多容纳1个许可证的信号量池,初始可用数为0。这意味着默认情况下没有线程可以通过。
public static Semaphore sem = new Semaphore(0, 1);
接着,我们创建一个简单的辅助类 myThread,它封装了一个 Thread 对象,负责启动并执行我们的测试逻辑:
第2步:主线程分发任务
在 Main 函数中,我们先手动调用一次 sem.Release()。这是为了让第一个即将进入等待的线程能够成功获取到许可证并继续运行。随后,我们创建了四个不同的线程实例:
sem.Release(); // 提前放出一个票根
myThread mythrd1 = new myThread("Thrd #1");
// ... (thrd2, thrd3, thrd4)
mythrd1.thrd.Join(); // 等待每个子线程结束以查看日志输出
注意 Join() 的作用:它是为了保持主程序不立即退出,从而让我们能观察到控制台打印出的各个线程的运行轨迹。
第3步:线程尝试获取许可并模拟业务逻辑
在每个子线程的 run 方法中,核心逻辑如下:
if (mySemaphore.sem.WaitOne(1000))
{
Console.WriteLine(thrd.Name + "申请到许可……");
Thread.Sleep(500); // 模拟持有许可证时的工作负载
mySemaphore.sem.Release(); // 完成后归还许可证
}
else
{
Console.WriteLine(thrd.Name + " 超时退出……");
}
这里的关键点在于 WaitOne(1000):它限制了最长等待时间为1秒。对于“Thrd #2”、“Thrd #3”等后续线程来说,因为前面只有一个活跃的处理过程,其他线程会在1秒后判定超时而放弃。这展示了在高并发场景下防止无限期挂起的一种策略。
完成判断
当程序运行时,如果你观察控制台输出,你将看到类似如下的现象:
- 首个参与者:通常最先启动或被调度到的线程会立刻获得由
Main函数提前发出的许可。 - 竞争者:其余未能在1秒内抢到信号的线程都会触发 else 分支,打印超时信息。
- 安全性:如果在代码中多次连续调用
Release导致总计数超过设定的上限(本例为1),程序将抛出异常。因此,务必保证Release的次数不超过实际占用次数加初始值。
简要总结
通过上面的步骤,我们利用 C# 提供的 Semaphore 类完成了对线程间共享资源的简单保护。它不仅限于互斥锁的独占模式,更能灵活设定并发数量,适用于数据库连接池管理、文件IO限流等多种场合。以上就是 C# 信号量 (Semaphore) 的详细用法,更多关于 .NET 并发编程的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









