threading.Event、Lock、Queue 分别解决什么问题?
这三者解决的并发问题截然不同,但在脑里分清楚之后,写多线程就不会乱塞锁。
🔐 Lock:解决“此时此地只能我一个人”¶
典型场景:抢票、计数器、写日志文件。
import threading
lock = threading.Lock()
seats = 10
def buy(n):
global seats
with lock: # 拿到钥匙才能往下走
if seats >= n:
seats -= n
print(f"出票{n}张,剩余{seats}")
-
📛 不加锁:线程A、B同时看到剩余10张,都认为自己能买,结果超卖。
-
✅ 加了锁:把“查余量→减库存”变成原子块,同一时刻只有一个线程在执行。
🧠 记法:Lock 是互斥,保护的是一块代码不被同时进入,核心是 acquire/release。
🚩 Event:解决“我好了,你们可以动了”¶
典型场景:服务就绪通知、主线程等所有子线程初始化完毕、定时器唤醒。
ready = threading.Event()
def worker():
print("worker 正在加载模型...")
time.sleep(3)
ready.set() # 🟢 举旗:我准备好了
def boss():
print("boss 等工人就绪...")
ready.wait() # 🔴 死等,直到旗子举起
print("开始干活!")
-
event.wait()阻塞当前线程,直到另一线程调用event.set()。 -
event.clear()可以把旗子降下来,用于循环事件。 -
与
Lock的本质区别:Event是跨线程的布尔信号,不保护任何共享对象,只管“行不行”的状态传递。
🚦 记法:Event 像红绿灯,红灯(未set)就停,绿灯(set)就走。
📮 Queue:解决“我生产,你消费,别直接握手”¶
典型场景:爬虫(一个线程抓链接,多个线程解析)、AI Agent 里消息队列、流水线。
from queue import Queue
import threading
q = Queue(maxsize=10)
def producer():
for i in range(20):
q.put(i) # 队列满则自动阻塞等待
print(f"生产 {i}")
def consumer():
while True:
item = q.get() # 队列空则自动阻塞等待
print(f"消费 {item}")
q.task_done()
-
自带内部锁,天然线程安全。
-
自带背压控制:满了
put阻塞,空了get阻塞,不用额外写wait/notify。 -
可以设置超时,可以用
join等待所有任务完成。
📮 记法:Queue 就是线程间的管道,生产和消费完全解耦,谁也不用知道对方的存在。
🧩 一图对比¶
| 工具 | 核心问题 | 机制 | 常见坑 |
|---|---|---|---|
| 🔐 Lock | 数据竞争 | 互斥锁,同一时刻一个线程 | 死锁、忘记释放 |
| 🚩 Event | 状态同步 | 布尔信号,跨线程通知 | 忘记 set 导致永久等待 |
| 📮 Queue | 数据传递 | 线程安全队列,阻塞式存取 | 无限 put 撑爆内存(设maxsize) |
🧠 选型直觉¶
-
变量被多个线程同时修改 → 上
Lock -
一个线程要等另一个线程完成某件事 → 上
Event -
多个线程要交换数据、流水线处理 → 上
Queue
线程协作出bug时,我一般先问自己:“是在抢资源,在等信号,还是在传东西?” 答案一出,工具也就跟着出来了。