FIFO
在了解Queue之前,首先应该对最基本的队列模型:FIFO有一个大致的了解。
First In First Out ,先入先出队列。FIFO 本质上只有两种操作,读&写,分别从队列的两端读取第一个数据,写入最后一个数据。
FIFO 在数据结构课上最先和大家见面,广泛用于计算机程序和结构中,在 FPGA 中的 FIFO 的含义和软件中的 FIFO 完全相同,只不过更加贴近硬件的实现。在数据缓冲,跨时钟域处理中将会大量运用到 FIFO 结构
Queue
Python中,队列是线程间最常用的交换数据的形式。Queue模块是提供队列操作的模块,即提供了一个适用于多线程编程的先进先出的数据结构,用来在生产者和消费者线程之间的信息传递
基本FIFO队列
使用方法:class Queue.Queue( maxsize=0 )
- maxsize指明了队列中能存放的数据个数的上限。
- 一旦达到上限,插入会导致阻塞,直到队列中的数据被消费掉。
- 如果maxsize小于或者等于0,队列大小没有限制
try:
import queue
except ImportError:
import Queue as queue
q = queue.Queue()
for i in range (5):
q.put(i)
while not q.empty():
print(q.get())
输出结果:
0
1
2
3
4
这里有个非常恶心的坑就是python3导入队列queue模块在目前尝试的办法和1个小时的百度结果内貌似都只能用上面的导入方法进行模块添加,如果直接采用import Queue的方法只能对python2起作用
但是如果将参数设置为<5的非零整数,就出现阻塞现象,显示程序一直在运行,没有结果输出。
那既然这样为什么还要设计这个“阻塞”的功能呢?不要急我们先了解一下“阻塞”:
几种阻塞队列
在 queue 模块下主要提供了三个类,分别代表三种队列,它们的主要区别就在于进队列、出队列的不同。关于这三个队列类的简单介绍总结如下:
- queue.Queue(maxsize=0):代表 FIFO(先进先出)的常规队列(就是刚刚的例子),maxsize 可以限制队列的大小。如果队列的大小达到队列的上限,就会加锁,再次加入元素时就会被阻塞,直到队列中的元素被消费。如果将 maxsize 设置为 0 或负数,则该队列的大小就是无限制的。
- queue.LifoQueue(maxsize=0):代表 LIFO(后进先出)的队列,与 Queue 的区别就是出队列的顺序不同。
- PriorityQueue(maxsize=0):代表优先级队列,优先级最小的元素先出队列。
这三个队列类的属性和方法基本相同, 它们都提供了如下属性和方法(大致了解即可,用的时候查就行):
- Queue.qsize():返回队列的实际大小,也就是该队列中包含几个元素。
- Queue.empty():判断队列是否为空。
- Queue.full():判断队列是否已满。
- Queue.put(item, block=True, timeout=None):向队列中放入元素。如果队列己满,且 block 参数为 True(阻塞),当前线程被阻塞,timeout 指定阻塞时间,如果将 timeout 设置为 None,则代表一直阻塞,直到该队列的元素被消费;如果队列己满,且 block 参数为 False(不阻塞),则直接引发 queue.FULL 异常。
- Queue.put_nowait(item):向队列中放入元素,不阻塞。相当于在上一个方法中将 block 参数设置为 False。
- Queue.get(item, block=True, timeout=None):从队列中取出元素(消费元素)。如果队列已满,且 block 参数为 True(阻塞),当前线程被阻塞,timeout 指定阻塞时间,如果将 timeout 设置为 None,则代表一直阻塞,直到有元素被放入队列中; 如果队列己空,且 block 参数为 False(不阻塞),则直接引发 queue.EMPTY 异常。
- Queue.get_nowait(item):从队列中取出元素,不阻塞。相当于在上一个方法中将 block 参数设置为 False。
所以queue 模块下提供了几个阻塞队列,通过“阻塞”可以使这些队列有选择的被订阅和发送,因此“阻塞”可以用于实现多线程(Threading)的通信功能。
Threading完成Queue的多线程小程序
threading提供了一个比thread模块更高层的API来提供线程的并发性。这些线程并发运行并共享内存。
一个对比实验
Thread的使用 目标函数可以实例化一个Thread对象,每个Thread对象代表着一个线程,可以通过start()方法,开始运行。
我们首先对使用多线程并发和不适用多线程并发做个比较,方便对多线程有一个初步的了解:
A、不使用多线程
import time
t0 = time.clock()
def worker():
print ("worker")
time.sleep(1)
return
if __name__ == "__main__":
for i in range(5):
worker()
print (time.clock()-t0)
输出:
worker
worker
worker
worker
worker
5.0032907999998315
B、使用多线程
import time
import threading as td
t0 = time.clock()
def worker():
print ("worker")
time.sleep(1)
return
if __name__ == "__main__":
for i in range(5):
t = td.Thread(target=worker)
t.start()
print (time.clock()-t0)
输出:
worker
worker
worker
worker
worker
0.01903249999986656
使用了多线程并发的操作,花费时间要短的很多。
threading.Thread
Thread 是threading模块中最重要的类之一,可以使用它来创建线程。有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法;另一种是创建一个threading.Thread对象,在它的初始化函数(init)中将可调用对象作为参数传入。
具体介绍这篇文章写的很好:
https://blog.youkuaiyun.com/drdairen/article/details/60962439
下面是一个利用Queue的阻塞特性进行多线程通信的例程:
import threading
import time
try:
import queue
except ImportError:
import Queue as queue
#这个例子说明了
def productors(bq):
str_tuple = ("Python", "Kotlin", "Swift")
for i in range(999999):
print(threading.current_thread().name + "生产者准备生产元组元素!")
time.sleep(1);
# 尝试放入元素,如果队列已满,则线程被阻塞
bq.put(str_tuple[i % 3]) #取模,返回除法余数
print(threading.current_thread().name \
+ "生产者生产元组元素完成!")
def consumer1(bq):
#lock.acquire()
for i in range(6):
print(threading.current_thread().name + "消费者1准备消费元组元素!")
time.sleep(1)
# 尝试取出元素,如果队列已空,则线程被阻塞,返回false
t = bq.get()
print(threading.current_thread().name \
+ "消费者1消费[ %s ]元素完成!" % t)
#lock.release()
def consumer2(bq):
#lock.acquire()
for i in range(6):
print(threading.current_thread().name + "消费者2准备消费元组元素!")
time.sleep(1)
# 尝试取出元素,如果队列已空,则线程被阻塞
t = bq.get()
print(threading.current_thread().name \
+ "消费者2消费[ %s ]元素完成!" % t)
#lock.release()
#lock = threading.Lock()
# 创建一个容量为1的Queue
bq = queue.Queue(maxsize=1)
#bq = bq.lock
# 启动1-3个生产者线程
threading.Thread(target=productors, args=(bq, )).start()
threading.Thread(target=productors, args=(bq, )).start()
#threading.Thread(target=productors, args=(bq, )).start()
# 启动1-2个消费者线程
threading.Thread(target=consumer1, args=(bq, )).start()
threading.Thread(target=consumer2, args=(bq, )).start()
上面程序启动了三个生产者线程向 Queue 队列中放入元素,启动了三个消费者线程从 Queue 队列中取出元素。本程序中 Queue 队列的大小为 1,因此三个生产者线程无法连续放入元素,必须等待消费者线程取出一个元素后,其中的一个生产者线程才能放入一个元素
输出结果:
Thread-29生产者准备生产元组元素!
Thread-30生产者准备生产元组元素!
Thread-31消费者1准备消费元组元素!
Thread-32消费者2准备消费元组元素!
Thread-29生产者生产元组元素完成!
Thread-29生产者准备生产元组元素!
Thread-31消费者1消费[ Python ]元素完成!Thread-30生产者生产元组元素完成!
Thread-30生产者准备生产元组元素!
Thread-31消费者1准备消费元组元素!
Thread-32消费者2消费[ Python ]元素完成!
Thread-32消费者2准备消费元组元素!
Thread-29生产者生产元组元素完成!
Thread-29生产者准备生产元组元素!
Thread-31消费者1消费[ Kotlin ]元素完成!Thread-30生产者生产元组元素完成!
Thread-31消费者1准备消费元组元素!
Thread-30生产者准备生产元组元素!
Thread-32消费者2消费[ Kotlin ]元素完成!
Thread-32消费者2准备消费元组元素!
Thread-29生产者生产元组元素完成!
Thread-29生产者准备生产元组元素!
Thread-31消费者1消费[ Swift ]元素完成!Thread-30生产者生产元组元素完成!Thread-32消费者2消费[ Swift ]元素完成!
Thread-31消费者1准备消费元组元素!
Thread-32消费者2准备消费元组元素!
Thread-30生产者准备生产元组元素!
Thread-29生产者生产元组元素完成!
Thread-29生产者准备生产元组元素!
Thread-31消费者1消费[ Python ]元素完成!
Thread-31消费者1准备消费元组元素!
Thread-30生产者生产元组元素完成!Thread-32消费者2消费[ Python ]元素完成!
Thread-32消费者2准备消费元组元素!
Thread-30生产者准备生产元组元素!
Thread-29生产者生产元组元素完成!
Thread-29生产者准备生产元组元素!
Thread-31消费者1消费[ Kotlin ]元素完成!
Thread-31消费者1准备消费元组元素!
Thread-30生产者生产元组元素完成!Thread-32消费者2消费[ Kotlin ]元素完成!
Thread-30生产者准备生产元组元素!
Thread-32消费者2准备消费元组元素!
Thread-29生产者生产元组元素完成!
Thread-29生产者准备生产元组元素!
Thread-31消费者1消费[ Swift ]元素完成!
Thread-30生产者生产元组元素完成!
Thread-30生产者准备生产元组元素!
Thread-32消费者2消费[ Swift ]元素完成!
Thread-29生产者生产元组元素完成!
Thread-29生产者准备生产元组元素!
可以看出三个生产者线程都想向 Queue 中放入元素,但只要其中一个生产者线程向该队列中放入元素之后,其他生产者线程就必须等待,等待消费者线程取出 Queue 队列中的元素。
但是分析打印的结果,如果我希望在consumer1享受消费时不受到consumer2的打扰,怎么办呢?我们可以使用线程锁。
线程锁
锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。
上述程序加锁例程:
import threading
import time
try:
import queue
except ImportError:
import Queue as queue
#这个例子说明了
def productors(bq):
str_tuple = ("Python", "Kotlin", "Swift")
for i in range(9):
print(threading.current_thread().name + "生产者准备生产元组元素!")
time.sleep(1);
# 尝试放入元素,如果队列已满,则线程被阻塞
bq.put(str_tuple[i % 3]) #取模,返回除法余数
print(threading.current_thread().name \
+ "生产者生产元组元素完成!")
def consumer1(bq):
lock.acquire()
for i in range(6):
print(threading.current_thread().name + "消费者1准备消费元组元素!")
time.sleep(1)
# 尝试取出元素,如果队列已空,则线程被阻塞,返回false
t = bq.get()
print(threading.current_thread().name \
+ "消费者1消费[ %s ]元素完成!" % t)
lock.release()
def consumer2(bq):
lock.acquire()
for i in range(6):
print(threading.current_thread().name + "消费者2准备消费元组元素!")
time.sleep(1)
# 尝试取出元素,如果队列已空,则线程被阻塞
t = bq.get()
print(threading.current_thread().name \
+ "消费者2消费[ %s ]元素完成!" % t)
lock.release()
lock = threading.Lock()
# 创建一个容量为1的Queue
bq.lock = queue.Queue(maxsize=1)
bq = bq.lock
# 启动1-3个生产者线程
threading.Thread(target=productors, args=(bq, )).start()
threading.Thread(target=productors, args=(bq, )).start()
#threading.Thread(target=productors, args=(bq, )).start()
# 启动1-2个消费者线程
threading.Thread(target=consumer1, args=(bq, )).start()
threading.Thread(target=consumer2, args=(bq, )).start()
输出为:
Thread-21生产者准备生产元组元素!
Thread-22生产者准备生产元组元素!
Thread-23消费者1准备消费元组元素!
Thread-21生产者生产元组元素完成!
Thread-21生产者准备生产元组元素!
Thread-23消费者1消费[ Python ]元素完成!Thread-22生产者生产元组元素完成!
Thread-22生产者准备生产元组元素!
Thread-23消费者1准备消费元组元素!
Thread-23消费者1消费[ Python ]元素完成!Thread-21生产者生产元组元素完成!
Thread-23消费者1准备消费元组元素!
Thread-21生产者准备生产元组元素!
Thread-23消费者1消费[ Kotlin ]元素完成!Thread-22生产者生产元组元素完成!
Thread-22生产者准备生产元组元素!
Thread-23消费者1准备消费元组元素!
Thread-23消费者1消费[ Kotlin ]元素完成!Thread-21生产者生产元组元素完成!
Thread-21生产者准备生产元组元素!
Thread-23消费者1准备消费元组元素!
Thread-23消费者1消费[ Swift ]元素完成!Thread-22生产者生产元组元素完成!
Thread-22生产者准备生产元组元素!
Thread-23消费者1准备消费元组元素!
Thread-23消费者1消费[ Swift ]元素完成!Thread-21生产者生产元组元素完成!
Thread-24消费者2准备消费元组元素!
Thread-21生产者准备生产元组元素!
Thread-24消费者2消费[ Python ]元素完成!Thread-21生产者生产元组元素完成!
Thread-21生产者准备生产元组元素!
Thread-24消费者2准备消费元组元素!
Thread-24消费者2消费[ Kotlin ]元素完成!Thread-22生产者生产元组元素完成!
Thread-22生产者准备生产元组元素!
Thread-24消费者2准备消费元组元素!
Thread-24消费者2消费[ Python ]元素完成!Thread-21生产者生产元组元素完成!
Thread-21生产者准备生产元组元素!
Thread-24消费者2准备消费元组元素!
Thread-24消费者2消费[ Swift ]元素完成!Thread-22生产者生产元组元素完成!
Thread-22生产者准备生产元组元素!
Thread-24消费者2准备消费元组元素!
Thread-24消费者2消费[ Kotlin ]元素完成!Thread-21生产者生产元组元素完成!
Thread-21生产者准备生产元组元素!
Thread-24消费者2准备消费元组元素!
Thread-24消费者2消费[ Python ]元素完成!Thread-22生产者生产元组元素完成!
Thread-22生产者准备生产元组元素!
Reference
[1] https://blog.youkuaiyun.com/Goldxwang/article/details/77838072
[2] https://stackoverflow.com/questions/34296860
[3] https://morvanzhou.github.io/tutorials/python-basic/threading/4-queue/
[4] http://www.vuln.cn/8610
[5] https://blog.youkuaiyun.com/drdairen/article/details/60962439