queue是Python标准库中的线程安全队列,常用于多线程编程时

1. queue简介

假设程序中有两个线程:

1
2
线程 A:不断产生任务
线程 B:不断处理任务

如果只使用一个普通列表,则需要考虑锁、竞态条件、等待通知等等

而queue则可以实现上述功能,并且可以更加灵活

queue是Python标准库
可以直接:

1
import queue

queue中主要有四种队列类:

1
2
3
4
Queue
LifoQueue
PriorityQueue
SimpleQueue

最常用的是Queue,它是一个先进先出(FIFO)的队列

所以我们常用的导入方式为:

1
from queue import Queue,Empty,Full

Empty和Full是queue中定义的异常类

Empty是队列为空时抛出的异常

Full是队列满时抛出的异常

2. Queue():创建队列

函数签名:

1
2
3
queue.Queue(maxsize=0)

# maxsize:最大队列长度,默认为0也就是无限制

基本使用:

1
2
3
from queue import Queue

q = Queue() # 创建一个空队列

3. Queue.put():向队列中添加元素

函数签名:

1
Queue.put(item, block=True, timeout=None)

block:如果队列满了,则是否阻塞。如果为True,则当队列满时,会阻塞线程,直到队列出现空位;如果为False,队列满了则会抛出Full异常
timeout:如果block为True,则表示阻塞的时间,单位为秒

例子:

1
2
3
4
5
6
7
import queue

q = queue.Queue()

q.put(1) # 添加元素
q.put(2)
q.put(3)

4. Queue.get():从队列中取出元素

函数签名:

1
Queue.get(block=True, timeout=None)

block:如果队列为空,则是否阻塞。如果为True,则当队列为空时,会阻塞线程,直到队列出现元素;如果为False,队列为空则会抛出Empty异常
timeout:如果block为True,则表示阻塞的时间,单位为秒

例子:

1
2
3
4
5
6
7
8
9
10
11
import queue

q = queue.Queue()

q.put(1)
q.put(2)
q.put(3)

print(q.get()) # 输出1
print(q.get()) # 输出2
print(q.get()) # 输出3

5. Queue.get_nowait():从队列中取出元素,不阻塞

get_nowait()方法与get()方法类似,但是不会阻塞线程,如果队列为空,则会抛出Empty异常

基本等价于:

1
q.get(block=False)

例子:

1
2
3
4
5
6
7
8
from queue import Queue, Empty

q = Queue()

try:
data = q.get_nowait()
except Empty:
print("队列为空")

6. Queue.put_nowait():向队列中添加元素,不阻塞

put_nowait()方法与put()方法类似,但是不会阻塞线程,如果队列满了,则会抛出Full异常

基本等价于:

1
q.put(item, block=False)

7. Queue.empty():返回队列是否为空

empty()方法返回队列是否为空

例如:

1
2
3
4
5
6
from queue import Queue, Empty

q = Queue()

if q.empty():
print("队列为空")

empty()返回的是一个布尔值,如果队列为空,则返回True,否则返回False

但是多线程程序中,empty()并不完全可靠,因为可能empty()返回结果后,其他线程继续操作队列,导致队列状态与empty()的结果不一致。

因此,建议使用get_nowait()或get()方法,而不是empty()方法

8. Queue.full():返回队列是否满

full()方法返回队列是否满

例如:

1
2
3
4
5
6
from queue import Queue, Full

q = Queue(maxsize=2)

if q.full():
print("队列满")

full()返回的是一个布尔值,如果队列满,则返回True,否则返回False

与empty()类似,多线程环境中它只能返回调用那一刻的结果,不能保证下一条语句执行时状态没变化

因此,建议使用put_nowait()或put()方法,而不是full()方法

9. Queue.qsize():返回队列中元素的数量

qsize()方法返回队列中元素的数量

例如:

1
2
3
4
5
from queue import Queue

q = Queue()

print(q.qsize())

输出:

1
0

同样,多线程环境中,qsize()方法只能返回调用那一刻的结果,不能保证下一条语句执行时状态没变化

10. Queue.task_done():通知线程完成一个任务

task_done()方法通知线程完成一个任务

例如:

1
data = q.get()

只表示获取一个元素,但是并不表示完成一个任务

1
q.task_done()

表示完成一个任务

例子:

1
2
3
4
5
6
7
8
9
def worker():
while True:
task = q.get()

print("处理:", task)

time.sleep(1)

q.task_done()

11. Queue.join():等待队列中所有线程结束

join()方法等待队列中所有线程结束

join()和task_done()方法常常配合使用

例如:

1
2
3
4
5
q.put(1)
q.put(2)
q.put(3)

q.join()

等待队列中的所有任务全部被处理完成,也就是等待队列中所有任务都调用了task_done()方法

完整例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import threading
import time
from queue import Queue

q = Queue()

def worker():
while True:
task = q.get()

print("正在处理:", task)

time.sleep(1)

q.task_done()

threading.Thread(
target=worker,
daemon=True
).start()

for i in range(5):
q.put(i)

q.join()

print("所有任务处理完成")

简单理解:

1
2
3
4
5
6
7
8
9
10
11
put()

任务数量 +1

task_done()

任务数量 -1

join()

等待任务数量 == 0

如果任务完成后没有调用task_done()方法,Queue也不会阻止获取下一个任务,但Queue内部依旧会认为上一个任务没有完成,也就是说这种情况下调用join()方法程序会一直阻塞

所以在实际任务中,每一个任务最好都对应一个task_done()方法,并且在任务完成后调用task_done()方法