一、队列
- Python的Queue模块中提供了同步的、线程安全的队列类,包括1、FIFO(先入先出)队列Queue ,2、LIFO(后入先出),队列LifoQueue,3、优先级队列PriorityQueue,这些队列都实现了锁原理,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
- 初始化Queue()对象时(例如: q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上线。
问题:我们在学习线程的时候,遇到了一个问题,就是多线程共享全局变量的时候,对同一个全局变量操作的时候,会出现资源竞争的问题,导致我们的数据不准确,因为线程切换的话是由我们python的GL锁去控制的,如果你有这个锁你才能去执行,如果说你没有获取到这个锁的话,你就没有办法执行。
当时我们怎么解决的?是添加锁去解决这个多线程共享全局变量导致资源竞争这个问题。
我们现在还可以通过队列去解决这个问题。
(1)什么是队列?
from queue import Queue, LifoQueue, PriorityQueue
我们先讲Queue这个队列,称之为先入先出队列。我们把队列想象成一个管子,先放进去的数据放在最右边,依次从右往左排列,出来的时候看图,大家也能看的明白,肯定是1先出来。,所以称之为谁先进来谁就先出去,先入先出。
(2)如何往队列里添加数据?
- 实例化一个队列对象 q = Queue(5) ,参数maxsize默认为不限制
- 通过 队列对象.put方法往里添加数据, q.put('python001')
from queue import Queue, LifoQueue, PriorityQueue
# 实例化一个队列对象
q = Queue(5) # 参数maxsize默认为不限制
# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006')
这个时候是没有问题的,但是如果我们继续往队列 q 里面添加数据,去执行的时候,程序就处于一种堵塞的状态,一直显示在运行中,为什么呢? 因为你设置的这个队列 最大存取5个数据 q = Queue(5) ,所以就会出现这个情况。
(3)队列满了,设置一个等待时间,不想让他一直处于堵塞的状态。
我们通过设置 q.put('python006', timeout=3), 添加了一个参数 timeout=3,设置等待时间3秒,等待三秒后队列还是满的话就报错,queue.Full,队列已满。
from queue import Queue, LifoQueue, PriorityQueue
# 实例化一个队列对象
q = Queue(5) # 参数maxsize默认0
# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)
# 报错打印结果
Traceback (most recent call last):
File "E:\pycharm\testing_and_development\py09_11day\demo1_python内置的队列模块.py", line 17, in <module>
q.put('python006', timeout=3)
File "E:\python\lib\queue.py", line 148, in put
raise Full
queue.Full
(4)获取队列中数据的长度 q.qsize()
我们把maxsize限制去掉,然后通过q.qsize()打印显示有6条数据。
from queue import Queue, LifoQueue, PriorityQueue
# 实例化一个队列对象
q = Queue() # 参数maxsize默认0
# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)
# 获取队列数据的长度
print(q.qsize())
# 打印结果
6
(5)获取队列中的数据 q.get()
我们看打印的是多少? 打印的是python001,这就验证了刚才咱们说的,先进去的数据先出来
我们把他依次获取出来,看看打印结果,嗯6条数据全部出来了。
from queue import Queue, LifoQueue, PriorityQueue
# 实例化一个队列对象
q = Queue() # 参数maxsize默认0
# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)
# 获取队列数据的长度
# print(q.qsize())
# get: 获取数据
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# 打印结果
python001
python002
python003
python004
python005
python006
我们把六条数据全部打印出来了,那如果说 我们继续打印会怎么样呢?
看代码我们看出来,打印6条数据之后,我们再去获取队列中的数据的长度,显示为0。然后我们再去获取 q.get()同样也会阻塞,大家看程序就阻塞在那边。
(6)往队列里面添加数据,他不会阻塞,不会等,满了就直接报错,q.put_nowait('python007')
这个方法往队列里面添加数据,他不会阻塞,不会等,前面提到的方法(q.put('python006', timeout=3))的话 他会等。
(7)获取数据不等待,如果没有数据了直接报错 q.get_notwait()
其实就是对put ,get方法做了一个封装,里面的参数block默认是True
(8)判断队列是否为空 q.empty()
from queue import Queue, LifoQueue, PriorityQueue
# 实例化一个队列对象
q = Queue() # 参数maxsize默认0
# 队列操作的第一个方法: put
q.put('python001')
q.put('python002')
q.put('python003')
q.put('python004')
q.put('python005')
q.put('python006', timeout=3)
# 获取队列数据的长度
# print(q.qsize())
# get: 获取数据
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.qsize())
# 判断队列是否为空
print(q.empty())
# 打印结果
python001
python002
python003
python004
python005
python006
0
True
(9)判断队列是否已满 q.full()
代码就不多于上传了,相信大家知道怎么玩了。
(10)join(等待队列中的任务执行完毕)、task_done(向队列发送一个任务执行完毕的信号。)
join:会去检查这个队列里面的任务是否执行完毕。
我们先看下下面的代码,我们看执行结果,发现执行到print("-----join 1------")之后,程序就阻塞了,一直阻塞在这里,这是为什么呢?
from queue import Queue
"""
task_done: 向队列发送一条任务执行完毕的消息
join: 等待队列中所有的任务是否执行完毕
"""
q = Queue(3)
q.put(111)
q.put(222)
q.put(33)
print("-----------join 1----------------")
q.join()
print("-----------join 2----------------")
我们上面解释到了,join的作用就是等待队列中的任务执行完毕,那我们看代码,我们在队列里面添加了三个数据111,222,33 ,那我们是不是应该把他拿出来,那这个队列里面的任务就执行完毕了?这只是我们猜想的结果,我们试一下。
我们发现代码依然会在这个地方卡住。这是为什么呢?
按理来说,我把三条数据全部获取出来了,应该不会卡了啊?这是为什么呢?
因为:join不是看这个队列里的任务数据有没有获取出来,也不是去看这个队列是否为空,他是去评判的是你这个任务被获取出去之后,也就是这个任务执行完毕之后,有没有向这个队列发送一个任务执行完毕的信号。
那怎么向这个队列发送一个任务执行完毕的信号? 用task_done,我们改下代码试试。
果然没错,代码也不会阻塞,join 2也打印出来了。
我如果少调用一次task_done,那程序就会卡住,这个我就不上传代码了,大家自行去试一下。
注意的地方:1.必须要等队列里面所有的数据全部获取出去,2.并且任务全部调用了task_done方法,代码才不会阻塞。
你看我只执行了2个,也会卡住,必须两者条件都要满足。
(11)后入先出队列
看打印结果很明显,就不做赘述了。(实际上LifoQueue类继承了Queue类,并自己实现了自己的四个私有方法)
from queue import LifoQueue, PriorityQueue
# 后入先出
q = LifoQueue()
q.put(11)
q.put(22)
print(q.get())
# 打印结果
22
(12)优先级队列(队列中添加的数据为元祖,元祖的第一个值用来设置队列的优先级)
根据你传进去的元祖,元祖里面第一个数值大小决定优先级,越小的先出来。
from queue import LifoQueue, PriorityQueue
# 优先级队列:(队列中添加的数据为元组,元组的一个值,用来设置队列的优先级)
q = PriorityQueue()
q.put((-15, '9999'))
q.put((77, '9999'))
q.put((4, '9999'))
q.put((99, '9999'))
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# 打印结果
(-15, '9999')
(4, '9999')
(77, '9999')
(99, '9999')
(13)通过队列保证线程数据的安全性
from threading import Thread
from queue import Queue
q = Queue()
q.put(0)
def work1():
for i in range(100000):
a = q.get()
a += 1
q.put(a)
def work2():
for i in range(100000):
a = q.get()
a += 1
q.put(a)
if __name__ == '__main__':
t1 = Thread(target=work1)
t1.start()
t2 = Thread(target=work2)
t2.start()
t1.join()
t2.join()
print("主线程---全局a:", q.get())
# 打印结果
200000