信号量¶
约 1636 个字 8 张图片 预计阅读时间 5 分钟
信号量 vs 互斥锁¶
简单来说:互斥锁(Mutex)是信号量(Semaphore)的一个特殊子集(二元信号量),信号量是互斥锁的通用扩展(计数信号量)。两者均用于解决多进程 / 多线程间的资源竞争问题,但适用场景、核心特性差异显著。
核心关系是:互斥锁 = 二元信号量(Binary Semaphore)
信号量的核心是一个**计数器 + P/V 操作**:
- P 操作(wait):计数器 - 1,若计数器 < 0 则阻塞;
- V 操作(post):计数器 + 1,若计数器≤0 则唤醒一个阻塞的进程 / 线程。
而互斥锁(Mutex)的本质是**计数器只能取 0 或 1 的信号量**:
- 锁空闲时,计数器 = 1(资源可用);
- 加锁(P 操作):计数器→0,其他线程 / 进程再加锁会阻塞;
- 解锁(V 操作):计数器→1,唤醒一个阻塞的线程 / 进程。
几乎所有操作系统中,互斥锁都是基于二元信号量实现的 —— 互斥锁的 “加锁 / 解锁” 等价于二元信号量的 “P/V 操作”,只是封装了更贴合 “独占资源” 的语义(如所有权、递归加锁等)。
两者对比:
| 特性 | 信号量(Semaphore) | 互斥锁(Mutex) |
|---|---|---|
| 计数器取值 | 非负整数(0,1,2,...N) | 仅 0/1(二元) |
| 核心用途 | 控制**多个**共享资源的访问(如 10 个连接池) | 保证**单个**资源的独占访问(如 1 个打印机) |
| 所有权 | 无所有权(任意线程 / 进程可释放) | 有所有权(仅加锁的线程 / 进程可解锁) |
| 递归加锁 | 支持(多次 P 操作,需对应 V 操作) | 部分支持(递归互斥锁),普通 Mutex 禁止递归加锁(会死锁) |
| 适用场景 | 进程 / 线程间同步、有限资源池控制 | 进程 / 线程间互斥、临界区保护 |
| 典型示例 | 限制同时访问数据库的连接数(如最多 5 个) | 保护单个文件的读写操作(同一时间仅 1 个线程写) |
关键差异拆解
-
“同步” vs “互斥”
-
信号量:主打**同步**(让多个进程 / 线程按顺序执行),也可实现互斥;例:生产者生产数据后(V 操作),消费者才能消费(P 操作),控制 “生产 - 消费” 的顺序。
-
互斥锁:主打**互斥**(保证同一时间只有一个执行者),无法实现复杂同步;例:多个线程写同一个文件,加互斥锁保证同一时间仅 1 个线程写,避免数据错乱。
-
-
所有权差异(最易踩坑)
-
信号量:A 线程 P 操作获取信号量,B 线程可 V 操作释放 —— 无所有权限制,易引发逻辑错误;
-
互斥锁:A 线程加锁,必须由 A 线程解锁(B 线程解锁会报错)—— 所有权保证更安全。
-
-
资源数量控制
-
信号量:可设置计数器 = N,允许 N 个线程 / 进程同时访问资源(如 N=5,最多 5 个线程用连接池);
-
互斥锁:仅允许 1 个线程 / 进程访问(计数器 = 1),本质是 “N=1 的信号量”。
-
多进程 / 多线程场景下的核心锁类型(按用途 + 特性分类)¶
在多进程 / 多线程同步场景中,锁的设计围绕 “资源独占、并发优化、场景适配” 三大目标,核心可分为 基础独占锁、并发优化锁、跨进程锁、特殊场景锁 四大类,下面按 “类型定义 + 核心特性 + 适用场景” 拆解,附选型优先级,新手也能快速匹配场景。
一、基础独占锁(核心基石,必掌握)¶
这类锁的核心是 “保证资源独占访问”,是所有同步锁的基础,适用于绝大多数基础场景。
| 锁类型 | 核心定义 | 核心特性 | 适用场景 |
|---|---|---|---|
| 互斥锁(Mutex) | 二元锁(0/1),同一时间仅 1 个线程 / 进程持有,保证资源独占 | ✅ 有所有权(仅加锁者可解锁) ✅ 获取失败则阻塞(让出 CPU) ❌ 不支持并行读 | 通用独占场景:文件读写、全局变量修改、临界区代码执行(读写均衡 / 写多读少) |
| 递归互斥锁(RLock) | 互斥锁的扩展,允许同一线程多次加锁(需对应次数解锁) | ✅ 解决 “递归调用加锁” 问题 ✅ 其他特性同 Mutex | 递归函数、嵌套加锁场景(如函数 A 调用函数 B,两者都需加锁) |
| 自旋锁(Spin Lock) | 忙等版互斥锁,获取失败时不阻塞,循环检测锁状态(不放弃 CPU) | ✅ 无上下文切换开销(性能极高) ❌ 自旋耗 CPU ✅ 多核场景优势明显 | 多核 CPU + 临界区极短(<10 微秒):内核态代码、高频内存操作、低延迟交易系统 |
关键提醒¶
- 自旋锁 仅适用于多核 + 短临界区,单核 / 长临界区使用会导致 CPU 100% 占用;
- 递归互斥锁需严格保证 “加锁次数 = 解锁次数”,否则会死锁。
二、并发优化锁(读多写少 / 多资源场景)¶
这类锁是基础锁的 “场景化优化”,解决基础锁 “全量串行” 导致的性能浪费问题。
| 锁类型 | 核心定义 | 核心特性 | 适用场景 |
|---|---|---|---|
| 读写锁(Read-Write Lock) | 分读锁(共享锁)、写锁(排他锁):多读并行、写写 / 读写互斥 | ✅ 读多写少场景性能远超 Mutex ✅ 可配置优先级(读优先 / 写优先) ❌ 实现复杂 | 读占比≥80% 的场景:配置中心、缓存查询、日志查看、商品库存查询 |
| 信号量(Semaphore) | 计数锁(0~N),控制同时访问资源的线程 / 进程数(本质是 “多资源版 Mutex”) | ✅ 无所有权(任意线程可释放) ✅ 支持同步 + 互斥 ❌ 易误释放导致异常 | 有限资源池控制:连接池(最多 10 个连接)、线程池、限流(最多 5 个并发请求) |
| 条件变量(Condition) | 基于 Mutex 的扩展,实现 “等待 - 唤醒” 机制(非纯锁,配合锁使用) | ✅ 精准唤醒指定线程 / 所有线程 ✅ 解决 “轮询等待” 问题 | 生产者 - 消费者模型、任务排队执行、线程间同步通信 |
关键提醒¶
- 信号量不是 “纯锁”,但常被归为同步锁范畴,不要用信号量实现简单互斥(无所有权,易出错);
- 读写锁避免 “读锁升级为写锁”(先加读锁再加写锁会导致死锁)。
三、跨进程锁(多进程场景专属)¶
多线程锁(如 Python threading.Lock)仅在进程内有效,跨进程需用内核级 / 文件级锁。
| 锁类型 | 核心定义 | 核心特性 | 适用场景 |
|---|---|---|---|
| 文件锁(fcntl/portalocker) | 基于文件的跨进程锁,通过内核维护文件锁状态 | ✅ 跨进程 / 跨机器(网络文件) ✅ 兼容所有系统 ❌ 性能略低(涉及文件 IO) | 多进程共享文件、跨进程资源独占(如多进程写同一个日志文件) |
| 系统 V 共享内存锁(shmctl) | 基于 System V 共享内存的跨进程锁 | ✅ 纯内存操作(性能高) ❌ 接口老旧、移植性差 | 老系统跨进程同步、高性能跨进程内存共享 |
| POSIX 互斥锁(PThread Mutex) | 跨进程的 POSIX 标准锁(需映射到共享内存) | ✅ 性能高、接口统一 ✅ 支持进程 / 线程级锁定 | 现代 Linux/Unix 多进程同步、跨进程临界区保护 |
| 分布式锁(Redis/ZooKeeper) | 跨机器 / 跨集群的分布式锁(非系统级锁,属于应用层锁) | ✅ 跨网络、跨集群 ✅ 支持超时、重入 ❌ 依赖中间件 | 分布式系统:分布式任务调度、跨机器资源独占、秒杀系统库存保护 |
关键提醒¶
- Python 多进程推荐用
multiprocessing.Lock(封装了 POSIX 锁),无需手动实现文件锁; - 分布式锁优先选 Redis(高性能)或 ZooKeeper(高可靠),避免手动实现(易出超时 / 死锁问题)。
四、特殊场景锁(解决特定问题)¶
这类锁针对 “死锁、公平性、优先级” 等特殊需求设计,属于进阶用法。
| 锁类型 | 核心定义 | 核心特性 | 适用场景 |
|---|---|---|---|
| 公平锁(Fair Lock) | 按 “先到先得” 顺序分配锁,避免线程饥饿 | ✅ 解决 “锁抢占不均” 问题 ❌ 性能略低(需维护等待队列) | 对公平性要求高的场景:任务调度、资源分配 |
| 重入锁(Reentrant Lock) | 同 “递归互斥锁(RLock)”,允许同一线程多次加锁 | ✅ 避免递归调用死锁 ✅ 有所有权保护 | 嵌套函数加锁、回调函数加锁 |
| 屏障锁(Barrier) | 等待所有线程到达指定点后,再一起执行 | ✅ 实现 “同步点” 机制 ✅ 支持超时、重置 | 多线程协同任务:数据分片计算(所有分片计算完成后汇总) |
| 死锁检测锁(Deadlock Detection Lock) | 内置死锁检测逻辑,触发死锁时报警 / 解锁 | ✅ 调试 / 监控场景实用 ❌ 性能开销大 | 开发 / 测试阶段、复杂锁嵌套场景(定位死锁问题) |
五、锁选型优先级(新手直接套用)¶
1. 多线程场景¶
- 通用独占 → Mutex(threading.Lock);
- 读多写少 → 读写锁(第三方库如
rwlock); - 递归 / 嵌套加锁 → RLock(threading.RLock);
- 多核 + 短临界区 → 自旋锁(Python 需模拟,C/C++ 原生支持);
- 线程同步通信 → Condition(threading.Condition)。
2. 多进程场景¶
- 简单跨进程独占 → multiprocessing.Lock;
- 多进程共享文件 → 文件锁(portalocker);
- 高性能跨进程内存共享 → POSIX 互斥锁 / System V 锁;
- 跨机器 / 集群 → Redis/ZooKeeper 分布式锁。
3. 避坑总原则¶
- 优先用 “最简单的锁”:能用水 Mutex 解决的,不用读写锁 / 信号量(减少复杂度);
- 跨进程锁优先选 “内核级”(如
multiprocessing.Lock),而非手动实现文件锁; - 自旋锁仅用于 “多核 + 极短临界区”,否则不如 Mutex 高效;
- 分布式锁必须加 “超时机制”,避免网络异常导致死锁。
总结¶
- 核心分类:基础独占锁(Mutex / 自旋锁)→ 并发优化锁(读写锁 / 信号量)→ 跨进程锁(文件锁 / 分布式锁)→ 特殊场景锁(公平锁 / 屏障锁);
- 选型核心:先看 “线程 / 进程”→ 再看 “读写比例 / 资源数量”→ 最后看 “特殊需求(公平性 / 递归)”;
- 关键提醒:锁的本质是 “牺牲并发换安全”,避免过度加锁(如无竞争的代码段加锁)。
并发机制¶




互斥量¶

TODO:底层原理是什么?如何阻塞?如何通知的?
条件变量¶
互斥量解决的是多线程并发读写的安全问题,条件变量解决的是多线程下协作的通知和等待问题。两者是配合关系,解决的不同问题。条件变量典型应用在生产消费问题上。


自旋锁¶
