c# Semaphore 和 Mutex 的区别 c#信号量有什么用

来源:这里教程网 时间:2026-02-21 17:36:54 作者:

什么时候该用
Semaphore
,而不是
Mutex

核心判断就一条:你要控制的是“最多 N 个线程同时干活”,还是“必须只有一个线程能进”。前者选

Semaphore
,后者才考虑
Mutex
(或更轻量的
lock
)。

Mutex
是“独占锁”——它只认“谁拿了,谁放”,且只允许一个线程持有;
Semaphore
是“配额计数器”——它不记谁拿了,只管还剩几个“入场券”。比如数据库连接池设为 10,第 11 个线程调用
WaitOne()
就得排队,不管前 10 个是谁。

需要跨进程互斥(如确保程序只启动一个实例)→ 必须用命名
Mutex
Semaphore
不行
要限制并发 HTTP 请求不超过 5 个 → 用
Semaphore(5, 5)
,别碰
Mutex
保护一个全局
int counter
被多线程累加 → 直接用
lock
Interlocked.Increment
Mutex
太重,
Semaphore
语义错

Semaphore
的典型用途和构造参数怎么填

Semaphore
的两个构造参数:初始可用数(
initialCount
)和最大容量(
maximumCount
),不是“当前用了几个”或“总共创建几个”,而是“一开始发几张票”和“最多能发多少张票”。

常见误写:

new Semaphore(0, 10)
—— 这等于开门就拒客,所有线程一来就阻塞,除非你后续主动
Release(10)
,否则永远卡死。

资源池场景(如连接池上限 8)→
new Semaphore(8, 8)
:起始全空闲,最多允许 8 并发
生产者-消费者缓冲区大小为 10 → 用两个信号量:
emptySlots = new Semaphore(10, 10)
(空位),
filledItems = new Semaphore(0, 10)
(已填项)
限流 API 调用(每秒最多 3 次)→ 配合
Timer
或令牌桶,但底层可基于
SemaphoreSlim
实现轻量等待

Semaphore
Mutex
最容易踩的坑

最危险的区别在“所有权”:

Mutex
只能由获取它的线程调用
ReleaseMutex()
,否则抛
ApplicationException
;而
Semaphore
任意线程都能调用
Release()
—— 这既是灵活性来源,也是 bug 温床。

误在非持有线程释放
Mutex
→ 程序崩溃或静默失败(尤其在异步/线程池回调中)
忘记
Release()
导致
Semaphore
一直被扣光 → 后续所有线程永久挂起,无异常提示
Semaphore(1, 1)
替代
Mutex
→ 功能看似一样,但失去所有权检查,调试时难定位谁没释放
跨进程场景硬上
Semaphore
→ 它不支持跨进程同步,命名无效,会静默创建多个独立实例

性能与替代选择:什么时候该换
SemaphoreSlim

如果你的场景纯属进程内(in-process)、高频率争抢(比如每毫秒几十次

WaitOne
),
Semaphore
的内核态开销明显高于
SemaphoreSlim
—— 前者每次调用都涉及系统调用,后者优先用自旋+用户态等待。

但注意:

SemaphoreSlim
不支持跨进程,也不支持等待超时时指定
CancellationToken
的某些高级行为(.NET 6+ 已补全),且不能用于
WaitHandle.WaitAll()
等 WaitHandle 组合操作。

Web API 限流、内存缓存并发读写控制 → 优先
SemaphoreSlim
需和
AutoResetEvent
等一起做 WaitHandle 数组等待 → 必须用
Semaphore
日志写入器要防多进程同时写同一文件 →
Mutex
(命名)才是正解,
SemaphoreSlim
根本无效

真正复杂的地方不在语法,而在于想清楚:你锁的到底是一个“门禁权限”,还是一批“限量门票”。混淆这两者,代码可能跑得通,但会在高并发或异常路径下突然卡死、数据错乱,而且极难复现。

相关推荐