Python的queue.Queue()是内置的线程安全队列,用于多线程环境下安全传递数据,是实现生产者消费者模式的标准工具。
Python queue() 到底怎么用
queue模块提供了三种队列:Queue(先进先出)、LifoQueue(后进先出)和PriorityQueue(优先级队列),最常用的是Queue,创建时通过maxsize参数控制最大容量,超出则put操作阻塞。
常用方法:
- put(item, block=True, timeout=None):将元素放入队列,队列满时阻塞。
- get(block=True, timeout=None):从队列取出元素,队列空时阻塞。
- task_done():在完成一项任务后调用,通知队列该任务已处理。
- join():阻塞直到队列中所有任务都调用了task_done。
简单示例:
import queue
q = queue.Queue(maxsize=10)
q.put('hello')
item = q.get()
print(item) # 输出 hello
get()默认阻塞直到有元素,设置block=False时,队列空会抛出queue.Empty异常;put同理,队列满抛出queue.Full。maxsize=0表示无限大小,但实际场景建议设一个合理上限,避免内存失控。
queue和deque区别:场景决定选择
很多同学会问,Python里已经有collections.deque(双端队列),为什么还要用queue.Queue?核心区别在于线程安全。
| 特性 | queue.Queue | collections.deque |
|---|---|---|
|
线程安全 | 是,内部使用锁和条件变量 | 否,单线程使用 |
| 阻塞行为 | put/get支持阻塞和超时 | 无阻塞,需手动轮询 |
| 适用场景 | 多线程生产者消费者 | 单线程栈/队列操作 |
| 性能 | 稍慢(有锁开销) | 快速 |
行业共识认为,在多线程中共享数据时,必须使用queue.Queue,因为deque的操作不是原子性的,可能导致数据竞争,如果你只在单线程内实现队列,deque是更好的选择速度更快,两端插入删除更灵活。
当多个线程同时put和get时,queue.Queue的阻塞机制能自动协调,而deque需要你自己加锁和条件变量,容易出错。大多数情况下,多线程队列优先选择 queue.Queue。
用queue队列实现生产者消费者模式
生产者消费者模式是经典的多线程协作方式,生产者生成数据,消费者处理数据,queue作为中间缓冲池。
完整示例:
import queue
import threading
import time
def producer(q):
for i in range(5):
item = f'data-{i}'
q.put(item)
print(f'生成: {item}')
time.sleep(1)
q.put(None) # 哨兵,表示结束
def consumer(q):
while True:
item = q.get()
if item is None:
break
print(f'消费: {item}')
q.task_done()
q = queue.Queue(maxsize=3)
t1 = threading.Thread(target=producer, args=(q,))
t2 = threading.Thread(target=consumer, args=(q,))
t1.start()
t2.start()
t1.join()
t2.join()
用None作为哨兵通知消费者退出,如果存在多个消费者,需要发送多个哨兵,更优雅的方式是结合threading.Event。
q.task_done()和q.join()配合可以确保所有任务完成后再退出,上面的例子没有使用join,因为消费者退出条件由哨兵控制,实际项目中,建议利用队列的join机制,或者配合阻塞特性和异常处理来设计退出逻辑。
queue() 多线程编程注意事项
线程安全原理:queue.Queue内部使用threading.Lock和threading.Condition,保证put和get操作的原子性,队列满时put等待,队列空时get等待。
阻塞参数:put和get都有block和timeout。block=True时,线程会阻塞直到条件满足或超时,超时后抛出queue.Empty或queue.Full。建议始终设置timeout,避免线程永久阻塞。
错误处理:调用get(block=False)时捕获Empty,put(block=False)时捕获Full,良好的异常处理能防止程序崩溃。
性能优化:队列大小影响性能,过大占用内存,过小导致频繁阻塞,根据任务生成速度和消费速度调整,使用queue.Queue而不是自己加锁,能大幅减少编码错误。
queue() 高级用法与变体
除了Queue,还有LifoQueue(后进先出)和PriorityQueue(优先级队列)。LifoQueue适合栈场景,PriorityQueue按优先级取出元素。
多进程队列:multiprocessing.Queue用于进程间通信,接口类似但基于管道和锁,如果使用多进程,需要导入multiprocessing而不是queue。
异步队列:asyncio.Queue用于协程之间,接口类似,但使用await put()和await get(),在异步编程中,queue.Queue不能用于协程,因为会阻塞事件循环。
Python queue() 常见问题解答
问题1:queue.put() 和 queue.get() 默认是阻塞的吗?
是,默认block=True,如果队列满或空,线程会阻塞直到条件满足,可以设置block=False或timeout来改变行为。
问题2:queue队列中的数据会不会丢失?
queue.Queue内部使用锁保护,数据在put和get之间不会丢失,但注意,如果消费者在处理数据时抛出异常,数据可能被丢弃(已从队列取出),建议在消费者内使用try-except,确保处理失败时重新放入队列或记录日志。
问题3:queue() 适合传输大量数据吗?
queue.Queue将数据保存在内存中,如果数据量极大,会占用大量内存,对于大数据量,建议使用消息队列中间件(如RabbitMQ、Kafka)或结合数据库,queue.Queue适合小到中等规模的数据传输,控制maxsize可以避免内存问题。
Python queue.Queue() 是多线程编程的基石,理解其用法和注意事项,能帮你写出健壮的生产者消费者代码。 从基础用法到高级变体,queue模块提供了灵活且线程安全的方案。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/508054.html



