《把并发拧成一股绳:一文讲透 Python 的 Lock、RLock、Semaphore(含实战模板与避坑清单)》

《把并发拧成一股绳:一文讲透 Python 的 Lock、RLock、Semaphore(含实战模板与避坑清单)》

面向对象:从并发入门的同学到需要把服务稳定跑在生产的工程师
目标:讲清 Lock / RLock / Semaphore 的设计哲学、使用边界与工程化最佳实践,配齐可直接落地的代码与可复用模板


开篇引入:为什么“锁”依然是并发世界的硬骨头?

Python 自 1991 年诞生,以“可读性至上”的设计征服了 Web、数据科学与 AI 工程。它被称为“胶水语言”,恰恰是因为能把数据库、消息队列、C 扩展库等多种组件优雅地粘合在一起。
但当你的程序开始多线程抓取写入共享缓存维护连接池时,数据一致性与临界区保护就成了绕不开的门槛。**锁(Locking)**是最基础、也最容易出错的工具:它强大但“多一分则阻塞,少一分则竞态”。这篇文章我会用通俗且“可复制”的方式,把 Lock、RLock 与 Semaphore 讲透,并给出能直接粘进你项目里的骨架代码。


目录速览

  1. 基础精要:核心概念与数据结构
  2. 三种锁的语义与对比(Lock/RLock/Semaphore)
  3. 代码示例:从“演示问题”到“工程模板”
  4. 进阶与实战:可观测性、超时、优雅关停与死锁排查
  5. 案例:连接池/限流、缓存击穿防护、日志顺序写
  6. 最佳实践清单与常见坑
  7. 前沿与展望:与 asyncio、多进程的协作边界
  8. 参考骨架与计时装饰器(保持文风一致)
  9. 总结与互动

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:           
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清水白石008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值