本质区别可以总结为:Mutex 是“所有权制”,而 Semaphore 是“信号制”。
1. 所有权 (Ownership):谁领的证,谁能离
这是两者最根本的区别。
-
Mutex(互斥锁): 具有 Ownership。当一个线程成功获取 Mutex 后,它就变成了该锁的“主人”。只有这个主人能解开这把锁。
- 语义: “这是我的私人房间,我进去后反锁了,必须我出来才能开锁。”
-
Semaphore(信号量): 没有 Ownership。它只是一个原子计数器。任何线程都可以对它进行
signal(加 1),即使这个线程之前从未执行过wait。- 语义: “这更像是一个限流器或红绿灯。任何人都可以去按那个变绿灯的按钮,不管是谁之前把它变红的。”
2. 优先级继承 (Priority Inheritance)
由于 Mutex 知道是谁持有了锁,它能解决优先级反转问题,而 Semaphore 不能。
- Mutex 的智能: 如果高优先级线程 H 在等低优先级线程 L 释放锁,内核会感知到 L 拥有这把锁,于是临时提升 L 的优先级。
- Semaphore 的盲目: 信号量内部不记录持有者。当高优先级线程 H 在
wait时,内核只看到 H 被挂起了,但不知道该去“催”谁。如果此时有一个中等优先级的线程 M 抢占了 L,H 就会被无限期阻塞。
3. 递归性 (Recursion)
- Mutex: 很多 Mutex 实现(如
NSRecursiveLock)支持递归加锁。因为它记录了所有者,所以它知道:“哦,又是这个线程在重复要锁,我让他过。” - Semaphore: 绝对不支持递归。如果你在持有信号量的情况下再次
wait,它只会简单地把计数器从 0 减到 -1 并把你挂起。你会自己把自己锁死。
4. 异常安全性 (Exception Safety)
在一些高级语言中,Mutex 与作用域(Scope)绑定。如果临界区内发生了异常,系统可以根据“所有权”自动帮已经死掉或退出的线程释放锁。
而信号量如果漏掉了 signal,该资源就永久丢失了,没有任何机制能自动恢复它,因为它没有“归属”概念。
5. 核心语义总结表
| 特性 | Mutex (互斥锁) | Semaphore = 1 (信号量) |
|---|---|---|
| 底层核心 | 锁的所有权 (Ownership) | 计数器 (Counter) |
| 谁能释放 | 必须是加锁的那个线程 | 任何线程都可以 |
| 优先级继承 | 支持 (防范优先级反转) | 不支持 |
| 典型用途 | 保护共享资源、保护临界区 | 任务同步、限流、跨线程通知 |
| 递归调用 | 可配置支持 | 绝对不支持 (死锁) |
实战建议:
- 如果你是为了防止多个线程同时修改一个变量,请永远选择
os_unfair_lock或Mutex,而不是信号量。 - 如果你是为了让线程 A 等待线程 B 完成某个任务(跨线程同步),或者限制同时请求接口的数量,那么
dispatch_semaphore才是正确的工具。