用threading.Lock保护共享变量最直接有效:count += 1非原子操作,需用同一锁实例配合with语句锁定读-改-写全过程,避免竞态丢失;queue.Queue适用于生产者-消费者通信,threading.local()则提供线程隔离而非共享。

用 threading.Lock 保护共享变量最直接有效
多线程读写同一字典、列表或计数器时,count += 1 这种操作不是原子的——它实际拆成“读值→计算→写回”三步,多个线程交错执行就会丢数据。加锁是最常用且足够应对大多数场景的解法。
-
threading.Lock()必须在所有访问该资源的线程中使用同一实例,不能每个线程 new 一个新锁 - 务必用
with lock:语法,避免忘记unlock()导致死锁;手动调用acquire()/release()容易出错 - 锁粒度要合适:锁整个函数太重,锁单行语句太细;通常围绕“修改共享状态”的最小逻辑块加锁
import threading <p>shared_counter = 0 lock = threading.Lock()</p><p>def increment(): global shared<em>counter for </em> in range(100000): with lock: # ← 关键:只锁真正需要互斥的部分 shared_counter += 1
用 queue.Queue 替代手管列表做线程间通信
如果多个线程需要往同一个列表里塞数据、另一个线程消费,别用 list.append() + 手动加锁——queue.Queue 内部已做线程安全封装,且自带阻塞、超时、容量控制等能力,更可靠也更符合生产习惯。
-
queue.Queue的put()和get()方法默认线程安全,无需额外加锁 - 用
q.join()+q.task_done()可精确等待所有任务完成,比轮询len(my_list)更健壮 - 注意
queue.Empty和queue.Full是异常,不是返回值,需显式捕获
from queue import Queue <p>q = Queue()</p><p>def producer(): for i in range(5): q.put(i) # 安全入队</p><p>def consumer(): while True: try: item = q.get(timeout=1) print(item) q.task_done() # 标记完成 except queue.Empty: break
避免用 threading.local() 误当共享缓存
threading.local() 创建的是线程本地存储(TLS),每个线程看到的是独立副本——它解决的是“隔离”,不是“共享”。常见误区是以为把它当全局缓存用,结果发现数据根本不同步。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
- 适合场景:保存数据库连接、请求上下文、临时计算中间态等“本线程专用”数据
- 不适合替代
dict或list做跨线程状态同步;想共享就得走锁或Queue - 属性赋值必须在对应线程内进行,主线程设的值对子线程不可见
local_data = threading.local() <p>def worker(): local_data.id = threading.current_thread().ident # 各线程独立 print(local_data.id) # 不会冲突,但也不共享
Python 的 GIL 并不保证数据结构操作的线程安全
很多人误以为 CPython 有 GIL 就不用管并发问题。GIL 只保证单个字节码指令的原子性,而像 my_list.append(x)、my_dict[key] = value 这些看似简单的操作,在字节码层面都可能拆成多步,尤其涉及扩容、哈希碰撞、引用计数变更时,依然会出错。
- GIL 被释放的时机包括 I/O、sleep、显式调用
time.sleep()或某些 C 扩展函数,此时其他线程可抢占 - 即使纯计算循环,CPython 也会每执行约 100 次字节码就释放一次 GIL,提供调度机会
- 验证方式简单:启动 10 个线程各对同一
int变量加 100000,不加锁结果几乎必小于 1000000
竞争问题不会总复现,但一旦发生就难定位。别依赖“好像没出错”来判断是否安全。

















