直接用全局变量+自增会出错,因为counter += 1非原子操作,分读取、计算、写回三步,多线程可能同时读到相同旧值导致丢失一次递增;必须用threading.Lock全程保护读写。

为什么直接用全局变量 + 自增会出错
多个线程同时执行 counter += 1 时,实际分三步:读取当前值 → 计算新值 → 写回内存。如果两个线程几乎同时读到同一个旧值(比如 5),各自加 1 后都写回 6,就丢了一次递增。这不是偶发bug,而是必然发生的竞态条件。
threading.Lock 的正确用法
必须确保所有读写共享变量的操作都被同一把锁保护,包括读取、修改、写入全过程。常见错误是只锁了写操作,或者在不同作用域用了不同锁实例。
- 锁对象要定义在全局或类实例属性中,不能在函数内每次新建
threading.Lock() - 用
with lock:最安全,自动处理 acquire/release,即使抛异常也不会漏释放 - 不要手动调用
lock.acquire()后忘记lock.release()
import threading <p>counter = 0 lock = threading.Lock()</p><p>def increment(): global counter for _ in range(100000): with lock: # ✅ 整个自增操作被原子化 counter += 1 </p>
性能代价和替代方案
加锁会让并发变成串行执行关键段,高竞争下吞吐明显下降。如果只是计数,threading.local() 或 queue.Queue 可避免锁,但语义不同:前者是线程私有,后者适合生产者-消费者模式。
- 需要全局一致值 → 必须用
Lock或RLock(可重入) - 只关心各线程独立计数 → 用
threading.local(),无锁且零开销 - 计数只是中间步骤,后续要聚合 → 先本地累加,最后用锁汇总一次
容易被忽略的初始化陷阱
如果计数器封装成类,锁必须在 __init__ 中创建,而不是作为类变量。否则所有实例共用一把锁,导致无关对象互相阻塞。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
class SafeCounter:
def __init__(self):
self.value = 0
self._lock = threading.Lock() # ✅ 实例独享锁
<pre class="brush:php;toolbar:false;">def incr(self):
with self._lock:
self.value += 1
多线程场景下,一致性比速度更难验证——问题往往只在压测时暴露,且难以复现。锁的位置、粒度、生命周期,一个没对齐就白忙活。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










