Queue(先进先出队列)
队列特点
队列特点:先进先出(FIFO, First in First Out)
–先进队列的元素先出队列。Queue
是python
标准库中的线程安全的队列实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递。
基本操作
class Queue.Queue(maxsize=0)Queue
提供了一个基本的FIFO
容器,使用方法很简单,maxsize
是个整数,指明了队列中能存放的数据个数的上限。一旦达到上限,插入会导致阻塞,直到队列中的数据被消费掉。如果maxsize
小于或者等于0
,队列大小没有限制。
1 | import queue |
先进先出原则第一次存放的是1,第二次存放的是2,那么我们在获取值得时候,第一次获取的就是1,第二次就是2。
看下面的例子如果队列里没有值怎么办?他会等待直到有数据为止:
1 | import queue |
如果不想让他等待,不管是否队列里都取数据,可以使用get_nowait( )
,但是如果队列中没有数据就会报错!
1 | import queue |
如果队列为空的时候可以通过异常处理进行捕获:
1 | import queue |
同样的如果队列长度为2,如果队列满了之后,同样他也是等待,直到有位置才会继续如下代码:
1 | import queue |
同样如果存放数值的时候如果不想让他等待,使用put_nowait( )
但是队列无法存放后会报错!
1 | import queue |
也可以使用empty()
方法判断队列是否为空
1 | import queue |
生产者消费者模型
什么是生产者消费者模型?
在工作中,大家可能会碰到这样一种情况:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产 生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。在生产者与消费者之间在加个缓冲区,我们形象的称之为仓库,生产者负责往仓库了进商 品,而消费者负责从仓库里拿商品,这就构成了生产者消费者模型。结构图如下:
生产者消费者模型的优点:
1、解耦
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化, 可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
举个例子,我们去邮局投递信件,如果不使用邮筒(也就是缓冲区),你必须得把信直接交给邮递员。有同学会说,直接给邮递员不是挺简单的嘛?其实不简单,你必须 得认识谁是邮递员,才能把信给他(光凭身上穿的制服,万一有人假冒,就惨了)。这就产生和你和邮递员之间的依赖(相当于生产者和消费者的强耦合)。万一哪天邮递员换人了,你还要重新认识一下(相当于消费者变化导致修改生产者代码)。而邮筒相对来说比较固定,你依赖它的成本就比较低(相当于和缓冲区之间的弱耦合)。
2、支持并发
由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区了拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。
接上面的例子,如果我们不使用邮筒,我们就得在邮局等邮递员,直到他回来,我们把信件交给他,这期间我们啥事儿都不能干(也就是生产者阻塞),或者邮递员得挨家挨户问,谁要寄信(相当于消费者轮询)。
3、支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。
为了充分复用,我们再拿寄信的例子来说事。假设邮递员一次只能带走1000封信。万一某次碰上情人节(也可能是圣诞节)送贺卡,需要寄出去的信超过1000封,这时 候邮筒这个缓冲区就派上用场了。邮递员把来不及带走的信暂存在邮筒中,等下次过来 时再拿走。
简单的生产者消费者模型:
1 | import queue |
LifoQueue(后进先出队列)
队列特点
队列特点:后进先出(LIFO, Last in First Out)
。与栈的类似,使用简单。
class Queue.LifoQueue(maxsize=0)
1 | import Queue |
PriorityQueue(优先级队列)
队列特点
队列特点: 根据优先级处理,数字最小的优先级最高。
class Queue.PriorityQueue(maxsize=0)
1 | import Queue |
结果:
1 | Job: level 3 job |
deque(双向队列)
队列特点
队列特点: 两边都能插入数据,都能取数据
1 | import queue |
常用方法
task_done( )
: 意味着之前入队的一个任务已经完成。由队列的消费者线程调用。每一个get( )
调用得到一个任务,接下来的task_done( )
调用告诉队列该任务已经处理完毕。
如果当前一个join( )
正在阻塞,它将在队列中的所有任务都处理完时恢复执行(即每一个由put( )
调用入队的任务都有一个对应的task_done( )
调用。
join( )
: 阻塞调用线程,直到队列中的所有任务被处理掉。
只要有数据被加入队列,未完成的任务数就会增加。当消费者线程调用task_done( )
(意味着有消费者取得任务并完成任务),未完成的任务数就会减少。当未完成的任务数降到0,join( )
解除阻塞。
put(item[, block[, timeout]])
: 将item
放入队列中。
- 如果可选的参数
block
为True
且timeout
为空对象(默认的情况,阻塞调用,无超时)。 - 如果
timeout
是个正整数,阻塞调用进程最多timeout
秒,如果一直无空空间可用,抛出Full
异常(带超时的阻塞调用)。 - 如果
block
为False
,如果有空闲空间可用将数据放入队列,否则立即抛出Full异常。
其非阻塞版本为put_nowait
等同于put(item, False)
get([block[, timeout]])
: 从队列中移除并返回一个数据。block
跟timeout
参数同put方法。
其非阻塞方法为get_nowait( )
相当与get(False)
empty( )
: 如果队列为空,返回True
,反之返回False
。
append(x)
: 将x添加到 deque
的右侧。
appendleft(x)
: 将x添加到 deque
的左侧。
clear( )
: 从留下长度为 0
的 deque
中移除所有元素。
count(x)
: 统计 deque
中值为x的个数。
extend(iterable)
: 通过追加元素从可迭代参数扩展的 deque
的右侧。
extendleft(iterable)
: 通过将附加元素从扩展 deque
的左侧可迭代。请注意,左边的一系列附加结果在扭转可迭代参数中元素的顺序。
pop( )
: 删除并从右侧的双端队列中返回的元素。如果没有元素存在,提出了IndexError
。
popleft( )
: 移除并返回一个元素从 deque 的左侧。如果没有元素存在,提出了IndexError
。
remove(value)
: 删除值的第一个匹配项。如果未找到,引发ValueError
。
reverse( )
: 翻转 deque
的元素,然后返回None
。
rotate(n)
: deque
中的元素向右移动n
个位置。如果n是负数的向左移动。向右移动一步相当于:d.appendleft(d.pop( ))
。
maxlen
: Deque
的最大长度。如果没有边界,则返回None
。
转载:梁先森 | Python之路:(十二)队列(queue)