queue 是 Python 内置的线程安全队列模块,核心作用是实现「先进先出」的数据存取,专门用来在多线程 / 多任务之间安全传递数据、调度任务。它自带锁机制,多个线程同时读写也不会出现数据错乱,是实现「生产者消费者模型」、任务排队、流量削峰的核心工具。
通俗理解:队列就像排队买奶茶 ------ 先排队的人先拿到奶茶,后到的人排在队尾;队列自动维护顺序,还能控制最大长度,满了就不让再排,空了就等新的人来。
本文从基础概念、三种核心队列、阻塞特性到经典应用,全程配可运行代码 + 逐行解释,零基础也能完全吃透。
一、前置基础概念
1. 什么是队列?
队列是一种「先进先出(FIFO = First In First Out)」的数据结构:
- 数据从队尾加入
- 数据从队头取出
- 先放进去的数据,一定会先被取出来
2. queue 模块的核心价值
- ✅ 线程安全:内置锁机制,多个线程同时读写队列,不会出现数据混乱、重复取数等问题
- ✅ 自带阻塞机制:队列空了自动等数据,队列满了自动等空位,不用自己写等待逻辑
- ✅ 多种队列类型:普通队列、栈、优先级队列,满足不同场景
- ✅ 解耦程序:把「生产数据」和「处理数据」拆开,两边速度不一致也能正常运行
3. 适用场景
- 多线程之间传递数据、分配任务
- 爬虫:URL 任务排队、结果收集
- 后台任务处理:比如订单处理、消息推送排队
- 流量削峰:短时间大量请求进来,排队慢慢处理
💡 新手注意: 我们说的
queue.Queue是线程间通信 用的;如果是多进程之间通信,要用multiprocessing.Queue,两者不能混用。
二、三种核心队列类
queue 模块提供了三种常用队列,用法几乎一致,只是进出顺序不同:
| 队列类 | 规则 | 通俗类比 | 适用场景 |
|---|---|---|---|
queue.Queue |
先进先出(FIFO) | 排队买饭,先到先得 | 绝大多数任务排队、消息传递 |
queue.LifoQueue |
后进先出(LIFO,栈) | 叠盘子,最后放的最先拿 | 深度优先搜索、撤销功能 |
queue.PriorityQueue |
按优先级排序,优先级高的先出 | VIP 通道,级别高的先办理 | 任务调度、紧急任务优先处理 |
1. Queue:普通先进先出队列(最常用)
这是最基础、最常用的队列,严格遵守先进先出规则。
① 创建队列
python
import queue
# 创建一个空队列,默认不限制最大长度(maxsize=0 表示无限大)
q = queue.Queue()
# 创建最多容纳 3 个元素的队列
q_limit = queue.Queue(maxsize=3)
maxsize参数:设置队列最大元素个数,超过后再放数据会阻塞等待。
② 核心常用方法
| 方法 | 作用 |
|---|---|
q.put(item) |
往队尾放入一个元素,队列满了会阻塞等待 |
q.get() |
从队头取出一个元素,队列空了会阻塞等待 |
q.empty() |
判断队列是否为空,返回 True/False |
q.full() |
判断队列是否已满,返回 True/False |
q.qsize() |
返回队列当前的元素个数 |
q.task_done() |
标记一个取出的任务已经处理完成 |
q.join() |
阻塞等待,直到队列中所有任务都被处理完 |
③ 基础操作完整示例
python
import queue
# 1. 创建队列,最多放3个元素
q = queue.Queue(maxsize=3)
print("===== 放入元素 =====")
# put() 往队尾放数据
q.put("任务1")
print(f"放入任务1,当前队列长度:{q.qsize()}")
q.put("任务2")
print(f"放入任务2,当前队列长度:{q.qsize()}")
q.put("任务3")
print(f"放入任务3,队列是否已满:{q.full()}")
print("\n===== 取出元素 =====")
# get() 从队头取数据,取一个少一个
item1 = q.get()
print(f"取出:{item1},剩余长度:{q.qsize()}")
item2 = q.get()
print(f"取出:{item2},剩余长度:{q.qsize()}")
item3 = q.get()
print(f"取出:{item3},队列是否为空:{q.empty()}")
运行结果:
python
===== 放入元素 =====
放入任务1,当前队列长度:1
放入任务2,当前队列长度:2
放入任务3,队列是否已满:True
===== 取出元素 =====
取出:任务1,剩余长度:2
取出:任务2,剩余长度:1
取出:任务3,队列是否为空:True
解释:放入顺序是 任务 1→任务 2→任务 3,取出顺序也是 任务 1→任务 2→任务 3,严格先进先出。
2. LifoQueue:后进先出队列(栈)
和普通队列正好相反:最后放进去的元素,第一个被取出来,也就是常说的「栈」结构。 用法和 Queue 完全一样,只是进出顺序不同。
示例
python
import queue
# 创建后进先出队列
lq = queue.LifoQueue()
# 依次放入 A B C
lq.put("A")
lq.put("B")
lq.put("C")
# 依次取出
print(lq.get()) # C
print(lq.get()) # B
print(lq.get()) # A
类比:叠盘子,最后放上去的盘子,我们会第一个拿走。
3. PriorityQueue:优先级队列
给每个元素设置优先级,优先级数字越小,优先级越高,越先被取出。不是按放入顺序,而是按优先级排序出队。
用法规则
- 放入的元素必须是元组形式 :
(优先级数字, 数据内容) - 数字越小,优先级越高
- 优先级相同的元素,会按数据内容的自然顺序排序
示例:任务优先级调度
python
import queue
# 创建优先级队列
pq = queue.PriorityQueue()
# 放入任务:(优先级, 任务名称)
# 数字越小,优先级越高
pq.put((3, "普通任务:整理报表"))
pq.put((1, "紧急任务:修复线上bug"))
pq.put((2, "重要任务:版本上线"))
pq.put((1, "紧急任务:处理用户投诉"))
# 依次取出,优先级高的先出
while not pq.empty():
task = pq.get()
print(f"执行:{task[1]},优先级:{task[0]}")
运行结果:
执行:紧急任务:修复线上bug,优先级:1
执行:紧急任务:处理用户投诉,优先级:1
执行:重要任务:版本上线,优先级:2
执行:普通任务:整理报表,优先级:3
解释:优先级 1 的紧急任务最先执行,然后是 2、3;同级别的按文字顺序排列。
⚠️ 新手坑:如果优先级相同,且后面的数据不能比较大小,会报错。所以同级数据尽量保证类型一致、可比较。
三、重点特性:阻塞与超时
队列最核心的特性就是「阻塞」,这也是它比普通列表好用的关键 ------ 不用自己写循环等待。
1. 什么是阻塞?
- put 阻塞 :队列满了的时候,再调用
put(),程序会停在这一行,一直等,直到队列有空位了才继续执行 - get 阻塞 :队列空了的时候,再调用
get(),程序会停在这一行,一直等,直到队列有新数据了才继续执行
新手很容易以为程序「卡死了」,其实是在正常等待数据。
2. 设置超时时间 timeout
不想一直等,可以给 put 和 get 加 timeout 参数,单位是秒:
- 超时时间内等到了:正常执行
- 超时了还没等到:抛出对应异常
① get 超时示例
python
import queue
q = queue.Queue()
try:
# 最多等2秒,还没数据就抛异常
data = q.get(timeout=2)
except queue.Empty:
print("等了2秒,队列还是空的,没有数据")
② put 超时示例
python
import queue
q = queue.Queue(maxsize=2)
q.put("A")
q.put("B")
try:
# 队列满了,最多等1秒,还没空位就抛异常
q.put("C", timeout=1)
except queue.Full:
print("等了1秒,队列还是满的,放不进去")
3. 非阻塞模式:立刻返回,不等待
如果不想等,没位置 / 没数据直接报错,可以用非阻塞写法:
q.put_nowait(item)等价于q.put(item, block=False)q.get_nowait()等价于q.get(block=False)
满了 / 空了会直接抛出 queue.Full / queue.Empty 异常,适合能接受失败、自己做重试的场景。
四、经典应用:生产者消费者模型
「生产者消费者模型」是队列最经典的应用场景,也是多线程编程的核心模式:
- 生产者:负责生成任务 / 数据,放进队列
- 消费者:负责从队列里取任务,处理任务
- 队列:中间的缓冲区,解耦生产和消费,平衡两边的速度
比如爬虫场景:
- 生产者线程:负责发现新的 URL,放进 URL 队列
- 消费者线程:从队列里取 URL,下载页面、解析数据
完整实战示例
python
import queue
import threading
import time
import random
# 1. 创建任务队列,最多放10个任务
task_queue = queue.Queue(maxsize=10)
# 2. 生产者函数:生成任务,放进队列
def producer(name):
for i in range(5):
task = f"{name}-任务{i}"
task_queue.put(task)
print(f"✅ {name} 生产了:{task},队列长度:{task_queue.qsize()}")
# 模拟生产速度随机
time.sleep(random.uniform(0.2, 0.5))
# 3. 消费者函数:从队列取任务,处理
def consumer(name):
while True:
# 从队列取任务,没任务就阻塞等
task = task_queue.get()
print(f"⚙️ {name} 处理了:{task}")
# 模拟处理耗时
time.sleep(random.uniform(0.3, 0.7))
# 标记任务处理完成
task_queue.task_done()
# 4. 启动线程
if __name__ == "__main__":
# 启动2个生产者
threading.Thread(target=producer, args=("生产者A",)).start()
threading.Thread(target=producer, args=("生产者B",)).start()
# 启动3个消费者,设为守护线程,主线程结束就自动结束
for i in range(3):
t = threading.Thread(target=consumer, args=(f"消费者{i+1}",), daemon=True)
t.start()
# 等待队列所有任务都处理完成
task_queue.join()
print("\n🎉 所有任务全部处理完成!")
代码逐行解释
- task_done():每处理完一个任务,必须调用一次,告诉队列「这个任务我做完了」
- join():阻塞等待,直到队列里所有任务都被标记为完成,才继续往下执行
- 守护线程 daemon=True:主线程结束后,消费者线程自动退出,不会一直卡在那等
- 解耦效果:生产者生产快、消费者处理慢也没关系,任务在队列里排队慢慢处理,不会丢失
五、新手高频易错点总结
1. 为什么不用普通列表 list?
很多新手会问:用 append() 加元素、pop(0) 取元素,不也能实现队列吗?
- 效率问题 :
list.pop(0)会把所有元素往前移,数据量大的时候速度很慢 - 线程安全:普通列表没有锁,多线程同时读写会出现数据错乱、重复取数等 bug
- 功能缺失:列表没有阻塞、超时、join 等高级功能,都要自己写
2. 队列空了程序卡住?
不是 bug,是 get() 的阻塞特性在等数据。
- 确定数据全部生产完了:用
join()等待所有任务处理完 - 不想一直等:加
timeout参数,或者用get_nowait()
3. PriorityQueue 优先级搞反
记住:数字越小,优先级越高。想让任务先执行,就给更小的数字。
4. 多进程用不了 queue.Queue
queue.Queue 只在同一个进程的多线程之间安全;多进程通信要用 multiprocessing.Queue,不然数据不通。
5. 忘记调用 task_done ()
如果用了 join() 等待,每个任务处理完必须调用 task_done(),否则 join() 会永远等下去,程序卡死。