《把并发拧成一股绳:一文讲透 Python 的 Lock、RLock、Semaphore(含实战模板与避坑清单)》
面向对象:从并发入门的同学到需要把服务稳定跑在生产的工程师
目标:讲清 Lock / RLock / Semaphore 的设计哲学、使用边界与工程化最佳实践,配齐可直接落地的代码与可复用模板
开篇引入:为什么“锁”依然是并发世界的硬骨头?
Python 自 1991 年诞生,以“可读性至上”的设计征服了 Web、数据科学与 AI 工程。它被称为“胶水语言”,恰恰是因为能把数据库、消息队列、C 扩展库等多种组件优雅地粘合在一起。
但当你的程序开始多线程抓取、写入共享缓存、维护连接池时,数据一致性与临界区保护就成了绕不开的门槛。**锁(Locking)**是最基础、也最容易出错的工具:它强大但“多一分则阻塞,少一分则竞态”。这篇文章我会用通俗且“可复制”的方式,把 Lock、RLock 与 Semaphore 讲透,并给出能直接粘进你项目里的骨架代码。
目录速览
- 基础精要:核心概念与数据结构
- 三种锁的语义与对比(Lock/RLock/Semaphore)
- 代码示例:从“演示问题”到“工程模板”
- 进阶与实战:可观测性、超时、优雅关停与死锁排查
- 案例:连接池/限流、缓存击穿防护、日志顺序写
- 最佳实践清单与常见坑
- 前沿与展望:与
asyncio、多进程的协作边界 - 参考骨架与计时装饰器(保持文风一致)
- 总结与互动
1) 基础精要:核心概念与数据结构
临界区(Critical Section):访问共享可变状态(如列表、字典、文件句柄、计数器)的代码片段。
互斥(Mutual Exclusion):同一时刻仅允许一个执行单元进入临界区。
锁(Lock)家族:
- Lock:最简单的二值锁,谁拿到谁进。
- RLock:可重入锁(Re-entrant),同一线程可重复获取,获取计数 +1。
- Semaphore:信号量,允许最多 N 个并发持有者,常用于连接池/限流。
注意:CPython 有 GIL,它保证同一时刻只有一个线程执行解释器字节码,但不能保证你的逻辑层面共享状态就安全。I/O 多线程仍常见,锁依然必要。
2) Lock / RLock / Semaphore:语义与对比
| 维度 | threading.Lock |
threading.RLock |
threading.Semaphore / BoundedSemaphore |
|---|---|---|---|
| 互斥粒度 | 1(互斥) | 1(互斥,可重入) | N(并发额度) |
| 可重入性 | 否 | 是(同线程可重复 acquire) | 无意义(配额计数) |
| 适用场景 | 简单临界区、无递归/回调 | 需要在同一调用链中多次进入同一临界区(递归、装饰器+内部调用) | 连接池、爬虫并发上限、批处理并发控制 |
| 死锁风险 | 低(设计简单,但易出现“不同锁顺序”问题) | 低(避免“同线程再次 acquire”自锁) | 中(滥用 release 导致计数失衡;BoundedSemaphore 可防溢出) |
| 超时支持 | acquire(timeout=...) |
同上 | 同上 |
| with 语法 | 支持 | 支持 | 支持 |
3) 代码示例:从“演示问题”到“工程模板”
3.1 入门:Lock 保护计数器(避免竞态)
import threading
counter = 0
lock = threading.Lock()
def inc(n=100_000):
global counter
for _ in range(n):
with lock: # 进入临界区
counter += 1
threads = [threading.Thread(target=inc) for _ in range(4)]
[t.start() for t in threads]
[t.join() for t in threads]
print("counter =", counter) # 期望:4 * 100000
要点:with lock 保证 += 1(读取-修改-写入)是原子的。
3.2 为什么需要 RLock:装饰器内外都要锁
场景:函数被装饰器加锁,但函数内部又调用了同一组需要保护的资源。如果用
Lock,同线程再次acquire会自锁;用RLock则安全。
import threading
from functools import wraps
rlock = threading.RLock()
def guarded(fn):
@wraps(fn)
def wrapper(*a, **k):
with rlock: # 第一次获取
return fn(*a, **k)
return wrapper
@guarded
def outer():
# 业务中又调用需要保护的 inner
inner()
def inner():
with rlock:

最低0.47元/天 解锁文章
》&spm=1001.2101.3001.5002&articleId=154517108&d=1&t=3&u=4f803d3f951b4e77a94a03ec4278ba40)
1448

被折叠的 条评论
为什么被折叠?



