必须用 queue.Queue,因其内置条件变量、原子操作和阻塞逻辑,天然线程安全;手动用 list+Lock 易漏边界检查致 IndexError 或死锁,且无内存屏障保障可见性。

用 queue.Queue 就够了,别自己手写线程安全队列。
为什么必须用 queue.Queue 而不是 list + threading.Lock
手动加锁容易漏掉边界:比如消费者在 list.pop(0) 前没检查是否为空,就会触发 IndexError;或者生产者没通知等待中的消费者,导致死锁。而 queue.Queue 内部已封装好条件变量、原子操作和阻塞逻辑,put() 和 get() 天然线程安全。
-
queue.Queue的get(block=True, timeout=None)会自动挂起线程直到有数据,不用轮询 -
put()在队列满时默认阻塞,避免内存无限增长(可配maxsize) - 所有操作都带内存屏障,不存在可见性问题
queue.Queue 的阻塞与超时怎么设才不卡死
关键在两个参数:是否阻塞(block)和等多久(timeout)。不设超时的阻塞调用,在程序退出时可能永远卡住——比如消费者还在 get(),但生产者已结束且没发终止信号。
- 正常工作流:用
q.get(timeout=1),捕获queue.Empty异常做空闲处理 - 优雅退出:生产者结束后往队列 put 一个特殊哨兵值(如
None),消费者收到后 break - 绝对避免:
q.get(block=True)无超时,尤其在线程 join 前
多消费者如何避免重复消费同一任务
queue.Queue 本身保证每个 get() 返回的任务只被一个线程拿到——它的内部计数器和 notify 机制是原子的。但要注意:如果消费者在处理任务中途崩溃,又没调用 task_done(),那么 join() 就永远不会返回。
立即学习“Python免费学习笔记(深入)”;
- 必须配对使用:
q.get()→ 处理 →q.task_done() - 用
try/finally包裹,确保即使异常也标记完成:try: item = q.get() process(item) finally: q.task_done() - 多个消费者共用一个队列时,
q.join()等待的是所有已 get 且未 task_done 的任务数归零,不是等待队列变空
最易忽略的一点:队列容量和线程数量要匹配。比如设 maxsize=1 却启 10 个生产者,9 个会立刻阻塞在 put();而消费者太少又会导致队列积压。实际部署前,先用小规模数据跑通 task_done 和哨兵退出路径,再扩并发。


















