threading.Lock在多线程对共享可变对象执行“读—改—写”操作时必须使用,如counter += 1或list.append(),因GIL不保证复合操作原子性,不加锁会导致数据丢失;正确做法是用with lock:包裹整个临界区。

threading.Lock 什么时候必须用?
当多个线程同时读写同一个可变对象(比如 list、dict、自定义类的属性)且涉及“读—改—写”操作时,threading.Lock 就不是可选项,而是必须项。典型场景:多个线程往同一个 shared_counter 变量上做 += 1;或往全局 results 列表里 .append() 数据。
Python 的 GIL 虽能保证单个字节码原子性,但 counter += 1 实际对应 LOAD → INPLACE_ADD → STORE 三步,中间可能被切换,结果就是丢数据——你启动 10 个线程各加 1000 次,最后 shared_counter 可能只有 7000 多。
Lock 的正确使用姿势
核心原则:锁的粒度要覆盖整个临界区,且必须成对出现。漏掉 release() 会死锁;提前 release() 会失去保护;没用 acquire() 就操作共享资源等于没锁。
- 推荐用
with lock:语句块——自动处理 acquire/release,哪怕中间抛异常也不会漏释放 - 不要手动调
lock.acquire()后忘记lock.release() - 避免在锁内做耗时操作(如网络请求、文件读写),否则其他线程会长时间阻塞
- 如果需要尝试获取锁而不阻塞,用
lock.acquire(blocking=False),它返回True或False,别直接当布尔值用而忽略返回值
示例:
import threading <p>shared_counter = 0 lock = threading.Lock()</p><p>def worker(): global shared<em>counter for </em> in range(1000): with lock: # ✅ 安全:自动加锁/解锁 shared_counter += 1</p><p>threads = [threading.Thread(target=worker) for _ in range(10)] for t in threads: t.start() for t in threads: t.join()</p><p>print(shared_counter) # 稳定输出 10000 </p>
Lock 和 RLock 的区别在哪?
普通 threading.Lock 不可重入:同一线程重复 acquire() 会死锁。而 threading.RLock(可重入锁)允许同一线程多次 acquire(),只要对应次数的 release() 即可解锁。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
常见误用场景:
- 递归函数里反复访问同一资源 → 必须用
RLock - 一个方法加了锁,内部又调用了另一个也加同样锁的方法 → 普通
Lock会卡住 - 性能上
RLock开销略大(需记录持有线程和计数),无必要时不替换
判断依据很简单:如果你的代码里出现了「同一个线程可能多次进入同一段临界区」,就换 RLock。
容易被忽略的坑:锁不是万能解药
加了 Lock 不代表并发安全就万事大吉。几个高频盲区:
- 锁只保护显式加锁的代码段,对锁外的共享变量读写完全不干预
- 如果多个逻辑上关联的变量(如
balance和currency)需要原子更新,但只锁了其中一个,依然会出错 -
Lock无法防止数据竞争以外的问题,比如线程间因执行顺序导致的逻辑错误(如检查后失效 TOCTOU) - 多进程场景下
threading.Lock完全无效,得用multiprocessing.Lock
最常被跳过的一步:确认你锁住的,确实是真正被并发修改的那个对象。有时候你以为在操作全局变量,实际是闭包里的局部引用,或者对象被意外复制了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










