python的队列+线程实验记录

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值