目录
Python队列Queue操作

Queue(先进先出队列)

队列特点

队列特点:先进先出(FIFO, First in First Out)–先进队列的元素先出队列。Queuepython标准库中的线程安全的队列实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递。

基本操作

class Queue.Queue(maxsize=0)
Queue提供了一个基本的FIFO容器,使用方法很简单,maxsize是个整数,指明了队列中能存放的数据个数的上限。一旦达到上限,插入会导致阻塞,直到队列中的数据被消费掉。如果maxsize小于或者等于0,队列大小没有限制。

1
2
3
4
5
6
import queue
q = queue.Queue() #调用队列生成对象
q.put(1) #存放第一个值到队列
q.put(2) #存放第二个值到队列
print('get frist one:%s' % q.get()) # 获取队列的第一个值
print('get second on:%s ' % q.get()) # 获取队列的第二个值

先进先出原则第一次存放的是1,第二次存放的是2,那么我们在获取值得时候,第一次获取的就是1,第二次就是2。

看下面的例子如果队列里没有值怎么办?他会等待直到有数据为止:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import queue
q = queue.Queue() # 调用队列生成对象
q.put(1) #存放第一个值到队列
q.put(2) #存放第二个值到队列
a = q.get() # 获取队列的第一个值
print('get frist one:%s' % a)
b = q.get() # 获取队列的第二个值
print('get second one:%s' % b)
c = q.get() # 获取队列的第三个值
print('get third one:%s' % c)
#结果:
'''
get frist one:1
get second one:2
#这里没有获取到值堵塞住,一直在等待着值进来~
'''

如果不想让他等待,不管是否队列里都取数据,可以使用get_nowait( ),但是如果队列中没有数据就会报错!

1
2
3
4
5
6
7
8
9
10
11
import queue
q = queue.Queue() # 调用队列生成对象
q.put(1) #存放第一个值到队列
q.put(2) #存放第二个值到队列
a = q.get() # 获取队列的第一个值
print('get frist one:%s' % a)
b = q.get() # 获取队列的第二个值
print('get second one:%s' % b)
c = q.get_nowait() # 获取队列的第三个值,使用:get_nowait() 不堵塞!
print('get third one:%s' % c)

如果队列为空的时候可以通过异常处理进行捕获:

1
2
3
4
5
6
7
8
9
10
11
12
13
import queue
q = queue.Queue() # 调用队列生成对象
q.put(1) #存放第一个值到队列
q.put(2) #存放第二个值到队列
try:
a = q.get() # 获取队列的第一个值
print('get frist one:%s' % a)
b = q.get() # 获取队列的第二个值
print('get second one:%s' % b)
c = q.get_nowait() # 获取队列的第三个值,使用:get_nowait() 不堵塞!
print('get third one:%s' % c)
except queue.Empty as q_error:
print('The Queue is empty!')

同样的如果队列长度为2,如果队列满了之后,同样他也是等待,直到有位置才会继续如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import queue
q = queue.Queue(2) # 调用队列生成对象,2:设置队列长度为2
q.put(1) # 存放第一个值到队列
print('put value 1 done')
q.put(2) # 存放第二个值到队列
print('put vlaue 2 done')
q.put(3) # 存放第三个值到队列
print('put value 3 done')
#结果:
'''
put value 1 done
put vlaue 2 done
#这里会一直等待~
'''

同样如果存放数值的时候如果不想让他等待,使用put_nowait( )但是队列无法存放后会报错!

1
2
3
4
5
6
7
8
9
10
import queue
q = queue.Queue(2) # 调用队列生成对象,2:设置队列长度为2
q.put(1) # 存放第一个值到队列
print('put value 1 done')
q.put(2) # 存放第二个值到队列
print('put vlaue 2 done')
# q.put(33, block=False) # 不堵塞
# q.put(33, block=False, timeout=2) # 不堵塞,等待2秒
q.put_nowait(3) # 存放第三个值到队列,使用:put_nowait() 不堵塞!
print('put value 3 done')

也可以使用empty()方法判断队列是否为空

1
2
3
4
5
6
7
8
9
10
import queue
# 队列最大长度,
# qsize()真实队列个数
# maxsize 最大支持的个数
q = queue.Queue(2) # 队列最大长度
print(q.empty()) # 检查是否为空(初始默认是空)
q.put(11)
q.put(22)
print(q.empty())
print(q.qsize()) # 获取队列长度

生产者消费者模型

什么是生产者消费者模型?

在工作中,大家可能会碰到这样一种情况:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产 生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。在生产者与消费者之间在加个缓冲区,我们形象的称之为仓库,生产者负责往仓库了进商 品,而消费者负责从仓库里拿商品,这就构成了生产者消费者模型。结构图如下:

生产者消费者模型

生产者消费者模型的优点:
1、解耦

假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化, 可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。

举个例子,我们去邮局投递信件,如果不使用邮筒(也就是缓冲区),你必须得把信直接交给邮递员。有同学会说,直接给邮递员不是挺简单的嘛?其实不简单,你必须 得认识谁是邮递员,才能把信给他(光凭身上穿的制服,万一有人假冒,就惨了)。这就产生和你和邮递员之间的依赖(相当于生产者和消费者的强耦合)。万一哪天邮递员换人了,你还要重新认识一下(相当于消费者变化导致修改生产者代码)。而邮筒相对来说比较固定,你依赖它的成本就比较低(相当于和缓冲区之间的弱耦合)。

2、支持并发

由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区了拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。

接上面的例子,如果我们不使用邮筒,我们就得在邮局等邮递员,直到他回来,我们把信件交给他,这期间我们啥事儿都不能干(也就是生产者阻塞),或者邮递员得挨家挨户问,谁要寄信(相当于消费者轮询)。

3、支持忙闲不均

缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。

为了充分复用,我们再拿寄信的例子来说事。假设邮递员一次只能带走1000封信。万一某次碰上情人节(也可能是圣诞节)送贺卡,需要寄出去的信超过1000封,这时 候邮筒这个缓冲区就派上用场了。邮递员把来不及带走的信暂存在邮筒中,等下次过来 时再拿走。

简单的生产者消费者模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import queue
import threading
import time
q = queue.Queue()
# 生成者(client)
def productor(arg):
# 序号加包子,将做好的包子放到篮子(队列)里
q.put(str(arg) + '包子')
# 创建30个包子
for i in range(30):
t = threading.Thread(target=productor, args=(i,))
t.start()
# ============================================================== #
# 消费者(server)
def consumer(arg):
while True:
# arg(0-3)吃包子得人, q.get()从篮子(队列)里取包子,包子有序号
print(arg, q.get())
time.sleep(2)
# 三个线程一起吃包子
for j in range(3):
t = threading.Thread(target=consumer, args=(j,))
t.start()

LifoQueue(后进先出队列)

队列特点

队列特点:后进先出(LIFO, Last in First Out)。与栈的类似,使用简单。
class Queue.LifoQueue(maxsize=0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Queue
q = Queue.LifoQueue()
for i in range(5):
q.put(i)
while not q.empty():
print q.get()

#结果:
'''
4
3
2
1
0
'''

PriorityQueue(优先级队列)

队列特点

队列特点: 根据优先级处理,数字最小的优先级最高。
class Queue.PriorityQueue(maxsize=0)

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
28
import Queue
import threading
class Job(object):
def __init__(self, priority, description):
self.priority = priority
self.description = description
print 'Job:',description
return
def __cmp__(self, other):
return cmp(self.priority, other.priority)

q = Queue.PriorityQueue()

q.put(Job(3, 'level 3 job'))
q.put(Job(10, 'level 10 job'))
q.put(Job(1, 'level 1 job'))
def process_job(q):
while True:
next_job = q.get()
print 'for:', next_job.description
q.task_done()
workers = [threading.Thread(target=process_job, args=(q,)),
threading.Thread(target=process_job, args=(q,))
]
for w in workers:
w.setDaemon(True)
w.start()
q.join()

结果:

1
2
3
4
5
6
Job: level 3 job
Job: level 10 job
Job: level 1 job
for: level 1 job
for: level 3 job
for: job: level 10 job

deque(双向队列)

队列特点

队列特点: 两边都能插入数据,都能取数据

1
2
3
4
5
6
7
8
9
10
import queue
q= queue.deque() #双向队列
q.append((123))
q.append(234)
q.appendleft(456)
print(list(q)) #运行结果:[456, 123, 234]
q.pop()
print(list(q)) #运行结果: [456, 123]
q.popleft()
print(list(q)) #运行结果:[123]

常用方法

task_done( ) 意味着之前入队的一个任务已经完成。由队列的消费者线程调用。每一个get( )调用得到一个任务,接下来的task_done( )调用告诉队列该任务已经处理完毕。
如果当前一个join( )正在阻塞,它将在队列中的所有任务都处理完时恢复执行(即每一个由put( )调用入队的任务都有一个对应的task_done( )调用。

join( ) 阻塞调用线程,直到队列中的所有任务被处理掉。
只要有数据被加入队列,未完成的任务数就会增加。当消费者线程调用task_done( )(意味着有消费者取得任务并完成任务),未完成的任务数就会减少。当未完成的任务数降到0,join( )解除阻塞。

put(item[, block[, timeout]])item放入队列中。

  1. 如果可选的参数blockTruetimeout为空对象(默认的情况,阻塞调用,无超时)。
  2. 如果timeout是个正整数,阻塞调用进程最多timeout秒,如果一直无空空间可用,抛出Full异常(带超时的阻塞调用)。
  3. 如果blockFalse,如果有空闲空间可用将数据放入队列,否则立即抛出Full异常。
    其非阻塞版本为put_nowait等同于put(item, False)

get([block[, timeout]]) 从队列中移除并返回一个数据。blocktimeout参数同put方法。
其非阻塞方法为get_nowait( )相当与get(False)

empty( ) 如果队列为空,返回True,反之返回False

append(x) 将x添加到 deque 的右侧。

appendleft(x) 将x添加到 deque 的左侧。

clear( ) 从留下长度为 0deque 中移除所有元素。

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)

文章作者: Kylen Chan
文章链接: https://booku.ltd/posts/pyqueue/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Kylen's Blog

评论