Python 中高级知识 queue
Queue
Queue是Python标准库中的线程安全的队列实现,提供了一个适用于多线程编程的有序队列结构,用来在生产者和消费者线程之间的信息传递.
模块实现了三种类型的队列,它们的区别仅仅是条目取回的顺序。在FIFO队列中,先添加的任务先取回。在LIFO队列中,最近被添加的条目先取回(操作类似一个堆栈)。优先级队列中,条目将保持排序(使用heapq模块) 并且最小值的条目第一个返回。
在内部,这三个类型的队列使用锁来临时阻塞竞争线程,保证多线程的安全;然而,它们并未被设计用于线程的重入性处理,所以在涉及锁重入的时候要格外注意
源码节选:
__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue', 'SimpleQueue'] |
Queue
FIFO 队列
Queue 提供了一个基本的 FIFO 容器,使用方法很简单,maxsize 是个整数,指明了队列中能存放的数据个数的上限。一旦达到上限,插入会导致阻塞,直到队列中的数据被消费掉。如果maxsize 小于或者等于 0,队列大小没有限制。
典型错误
# !/usr/bin/env python3# -*- coding: utf-8 -*from queue import Queueq = Queue(5)# put 5 次 后被阻塞,导致程序死锁for x in range(10): q.put(x)for x in range(10): print(q.get()) |
因为这个队列的最大容量为5, 连续放入超过5个的元素,并且没有其他线程去消费,会导致阻塞。比如上面的这段无法继续执行下去。这里是一个错误写法,初学者经常会犯这类问题。
正确修正写法
# !/usr/bin/env python3# -*- coding: utf-8 -*from queue import Queueq = Queue(15)for x in range(10): q.put(x)for x in range(10): print(q.get()) |
修正手段:扩大queue的容量到足够大行。其实queue的默认就是无穷大,直到机器的物理资源不够!
本质
queue 是一个生产-消费模型,像上面的代码,不是并发的出现生产和消费,很容易出现存满或者取空的情况
典型的操作类似这样
# !/usr/bin/env python3# -*- coding: utf-8 -*import randomfrom queue import Queuefrom threading import Thread# 生产者def producer(q: Queue): while True: i = q.put(random.randint(1, 10)) print("producer put ", i)# 消费者def consumer(q: Queue): while True: i = q.get() print("consumer get ", i)if __name__ == "__main__": q = Queue(5) p1 = Thread(target=producer, args=(q,)) c1 = Thread(target=consumer, args=(q,)) p1.start() c1.start() p1.join() c1.join() |
一些线程生产,一些线程消费
....consumer get 6producer put Noneconsumer get 10producer put Noneconsumer get 8producer put Noneconsumer get 7producer put Noneconsumer get 9producer put Noneconsumer get 9.... |
和get/put类似的是get_nowait/put_nowait,不同的是后者实际是不具备阻塞等待能力的,英文也暗示了。如果队列取空了再取/队列填满了再填就会报错
LifoQueue
LIFO 队列,除了元素出入顺序和queue不一样,其余的一样
PriorityQueue
优先级队列,可以根据你给的比较规则排序,使你取到的元素,总是当前比较排序出的最小的元素,排序规则是heapq模块的堆排序
将前面演示的代码略微改下,注意看下注释
# !/usr/bin/env python3# -*- coding: utf-8 -*import randomfrom queue import Queue, PriorityQueuefrom threading import Thread# 生产者def producer(q: Queue): while True: # 放入可排序的对象,这里是int,它可以排序 i = q.put(random.randint(1, 10)) if q.qsize() == 5: q.join() print("producer put ", i)# 消费者def consumer(q: Queue): while True: i = q.get() # 这里注释了,故意让q.join阻塞,方便看到排序后队列被消费,可以解屏蔽join就不会阻塞了 # q.task_done() print("consumer get ", i)if __name__ == "__main__": q = PriorityQueue(5) p1 = Thread(target=producer, args=(q,)) p1.start() c1 = Thread(target=consumer, args=(q,)) c1.start() p1.join() c1.join() |
元素在队列里面排序,然后被排序并消费
producer put Noneproducer put Noneproducer put Noneproducer put consumer get 1Noneproducer put Noneconsumer get 2consumer get 4consumer get 4consumer get 6consumer get 7 |
自定义排序规则
# !/usr/bin/env python3# -*- coding: utf-8 -*import randomfrom queue import Queue, PriorityQueuefrom threading import Threadclass MyValue: def __init__(self, priority, description): self.priority = priority self.description = description return # 重写<运算,供比较操作 def __lt__(self, other): # 改变这里的< 为 > ,优先级顺序就不一样了 return self.priority - other.priority < 0 def to_str(self): return "priority : " + str(self.priority) + "description: " + str(self.description)# 生产者def producer(q: Queue): while True: # 放入可排序的对象,这里是int,它可以排序 i = random.randint(1, 10) q.put(MyValue(i, "value" + str(i))) if q.qsize() == 5: q.join()# 消费者def consumer(q: Queue): while True: i = q.get() # 这里注释了,故意让q.join阻塞,方便看到排序后队列被消费,可以解屏蔽join就不会阻塞了 # q.task_done() print("consumer get ", i.to_str())if __name__ == "__main__": q = PriorityQueue(5) p1 = Thread(target=producer, args=(q,)) p1.start() c1 = Thread(target=consumer, args=(q,)) c1.start() p1.join() c1.join() |
SimpleQueue
其实使用和Queue没有区别,但是使用比Queue简单,什么参数都不需要传入,直接创建实例了就可以使用实例,有最基本的Queue的方法和特性
# !/usr/bin/env python3# -*- coding: utf-8 -*import randomfrom queue import Queue, SimpleQueuefrom threading import Thread# 生产者def producer(q: Queue): while True: i = q.put(random.randint(1, 10)) print("producer put ", i)# 消费者def consumer(q: Queue): while True: i = q.get() print("consumer get ", i)if __name__ == "__main__": q = SimpleQueue() p1 = Thread(target=producer, args=(q,)) c1 = Thread(target=consumer, args=(q,)) p1.start() c1.start() p1.join() c1.join() |
task_done/join
一般我们使用put/get就行了,为什么要有get后显式使用task_done,并期待在全部的get没有都完成task_done前,生产者使用join会阻塞?
本质还是生产-消费模型的考虑,一般的put/get模型,双方都是没有沟通的,彼此透明,不管对方。但是有这个有的情况,生产者的全速生产速度为10个/S, 消费者的消费全速为8个/S,如果继续采用put/get模型会出现生产-消费速度不一样的问题,生产者会生产过快填满队列或者队列越来越长指导硬件资源耗尽,有了task_done和join就做了一个很好的生产消费协调,生产者生产一段时间后可以自己按照条件检查并自己join阻塞自己暂停生产,等待消费者消费完后再次投入生产,是个很典型的类似限流的操作
FULL
对满的 Queue 对象,调用非阻塞的 put() (or put_nowait()) 时,引发的异常。
# !/usr/bin/env python3# -*- coding: utf-8 -*from queue import Queueq = Queue(4)for x in range(10): i = q.put(block=False, item=x) print(i) |
非阻塞填满后,异常返回
NoneNoneNoneNoneTraceback (most recent call last): File "C:\INSTALL\Python3\lib\queue.py", line 136, in put raise Fullqueue.Full |
EMPTY
对空的 Queue 对象,调用非阻塞的 get() (or get_nowait()) 时,引发的异常。
# !/usr/bin/env python3# -*- coding: utf-8 -*from queue import Queueq = Queue(4)for x in range(3): i = q.put(block=False, item=x) print(i)for x in range(5): i = q.get(block=False) print(i) |
非阻塞取空后,异常返回
Traceback (most recent call last): File "C:\INSTALL\Python3\lib\queue.py", line 167, in get raise Empty_queue.EmptyNoneNoneNone012 |

浙公网安备 33010602011771号