Python多进程Queue队列详解:参数、阻塞与非阻塞处理及生产者消费者模型
本文详解Python multiprocessing模块中Queue队列的实现原理与API用法。涵盖Queue初始化、put/get方法的block与timeout参数控制阻塞行为,以及put_nowait/get_nowait的非阻塞处理。通过完整代码实例演示单进程存取、子进程通信,并深入解析基于Queue与JoinableQueue的生产者消费者模型,解决进程同步与死锁问题。
进程间通信(IPC)概述
进程间通信(Inter-Process Communication,简称IPC)是操作系统中实现不同进程间数据交换的关键机制。
IPC(Inter-Process Communication)
IPC机制旨在实现进程之间的有效通讯。
管道(pipe):基于共享内存空间实现数据流动。
队列(queue):结合管道与锁机制的概念构建。
Queue队列基础概念
二、队列(Queue)
2.1 概念——multiprocessing.Queue
Queue用于创建共享的进程队列。它是一个多进程安全的队列对象,支持在多进程环境中安全地传递数据。
Queue([maxsize])创建共享的进程队列。
参数说明:maxsize指定队列允许的最大项数。若省略此参数,则队列大小无限制。
底层实现上,队列通过管道和锁机制来保证安全性。
Queue核心方法详解
2.2 Queue方法使用
2.2.1 q.get()的使用:
该方法从队列中取出值,并移除该值。若未指定参数,默认行为是阻塞等待,直到有数据可用。
即使队列中没有可取的值,程序也不会直接结束,而是处于阻塞等待状态。
from multiprocessing import Queue
q = Queue() # 生成一个队列对象
# put方法是往队列里面放值
q.put('Cecilia陈')
q.put('xuchen')
q.put('喜陈')
# get方法是从队列里面取值
print(q.get())
print(q.get())
print(q.get())
q.put(5)
q.put(6)
print(q.get())
Cecilia陈
xuchen
喜陈
5
2.2.2 Queue(参数) +参数的使用:
在Queue初始化时传入参数,该参数为数值类型。
该数值表示实例化的Queue队列最多可以存放的数据项数量。
当队列已满时,继续放入值会导致程序阻塞,但程序不会终止。
from multiprocessing import Queue
q = Queue(3)
q.put('Cecilia陈')
q.put('xuchen')
q.put('喜陈')
print(q.full()) # 判断队列是否满了 返回的是True/False
q.put(2) # 当队列已经满的时候,再放值,程序会阻塞,但不会结束
True 表示队列已经满了
2.2.3 q.put(参数1,参数2,参数3,参数4):
q.put(self, obj, block=True, timeout=None)
self:put是Queue类的一个方法,调用时q.put相当于队列对象q调用绑定方法,此参数可省略。
obj:需要放入队列的值。
block=True:若队列已满,put操作会等待,程序不会立即结束。
timeout=None:基于block参数,当block为True时,timeout指定等待的秒数。若超时后队列仍满,程序将抛出Queue.Full异常并结束。
from multiprocessing import Queue
q = Queue(3)
q.put('zhao',block=True,timeout=2)
q.put('zhao',block=True,timeout=2)
q.put('zhao',block=True,timeout=2)
q.put('zhao',block=True,timeout=5) # 此时程序将对等待5秒以后报错了
2.2.4 q.get(参数1,参数2,参数3,参数4):
q.get(self, block=True, timeout=None)
self:get是Queue类的方法,调用时q.get相当于队列对象q调用绑定方法,此参数可省略。
block=True:从队列q对象中取值,若取不到值,程序不会立即结束。
timeout=None:基于block参数,当block为True时,timeout指定等待的秒数。若超时后仍未取到值,程序将抛出queue.Empty异常并结束。
from multiprocessing import Queue
q = Queue()
q.put('Cecilia陈')
print(q.get())
q.get(block=True,timeout=2) # 此时程序会等待2秒后,报错了,队列里面没有值了
2.2.5 block=False:
若block值为False,put方法在队列满时不会等待阻塞,程序直接抛出Queue.Full异常并结束。
若block值为False,get方法在队列空时不会等待阻塞,程序直接抛出queue.Empty异常并结束。
1.put()的block=False
from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.full())
q.put('xichen',block=False) # 队列已经满了,我不等待了,直接报错
2.get()的block=Flase
from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.get())
print(q.get())
print(q.get(block=False)) # 队列已经没有值了,我不等待了,直接报错
2.2.6 put_nowait()/get_nowait()
1.put_nowait() 相当于block=False,队列满时,放入值不会等待阻塞,直接报错。
from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.full())
q.put_nowait('xichen') # 程序不等待,不阻塞,直接报错
2.get_nowait() 相当于block=False,队列空时,取值不会等待阻塞,直接报错。
from multiprocessing import Queue
q = Queue(2)
q.put('Cecilia陈')
q.put('喜陈')
print(q.get())
print(q.get())
print(q.full())
q.get_nowait()# 再取值的时候,程序不等待,不阻塞,程序直接报错
Queue代码实例与进程通信
三、代码实例
3.1 单看队列的存取数据用法
此示例未涉及进程通信,仅展示队列提供的方法及其使用现象。
'''
multiprocessing模块支持进程间通信的两种主要形式:管道和队列
都是基于消息传递实现的,但是队列接口
'''
from multiprocessing import Queue
q=Queue(3)
#put ,get ,put_nowait,get_nowait,full,empty
q.put(3)
q.put(3)
q.put(3)
# q.put(3) # 如果队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列。
# 如果队列中的数据一直不被取走,程序就会永远停在这里。
try:
q.put_nowait(3) # 可以使用put_nowait,如果队列满了不会阻塞,但是会因为队列满了而报错。
except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去,但是会丢掉这个消息。
print('队列已经满了')
# 因此,我们再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了。
print(q.full()) #满了
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) # 同put方法一样,如果队列已经空了,那么继续取就会出现阻塞。
try:
q.get_nowait(3) # 可以使用get_nowait,如果队列满了不会阻塞,但是会因为没取到值而报错。
except: # 因此我们可以用一个try语句来处理这个错误。这样程序不会一直阻塞下去。
print('队列已经空了')
print(q.empty()) #空了
3.2 子进程向父进程发送数据
这是一个queue的简单应用,使用队列q对象调用get函数来获取队列中最先进入的数据。
from multiprocessing import Process, Queue
def f(q,name,age):
q.put(name,age) #调用主函数中p进程传递过来的进程参数 put函数为向队列中添加一条数据。
if __name__ == '__main__':
q = Queue() #创建一个Queue对象
p = Process(target=f, args=(q,'Cecilia陈',18)) #创建一个进程
p.start()
print(q.get())
p.join()
['Cecilia陈', 18]
生产者消费者模型原理与实现
四、生产者消费者模型
生产者:负责生产数据的任务。
消费者:负责处理数据的任务。
生产者--队列(缓冲区)--->消费者
生产者可以持续生产以达到最大效率,消费者可以持续消费以达到最大效率。
生产者消费者模型显著提高了生产与处理的效率。
补充:queue不适合传输大文件,通常用于传递消息。
在并发编程中,使用生产者和消费者模式能解决大多数并发问题。该模式通过平衡生产线程和消费线程的工作负载来提高程序整体处理数据的速度。
4.1 为什么要使用生产者和消费者模型
在线程环境中,生产者是生成数据的线程,消费者是处理数据的线程。多线程开发中,若生产者速度快于消费者,生产者需等待消费者处理完毕才能继续生产;反之,若消费者速度快于生产者,消费者需等待生产者。为解决此耦合问题,引入了生产者和消费者模式。
4.2 什么是生产者消费者模型
生产者消费者模式通过一个容器解决生产者和消费者的强耦合问题。双方不直接通讯,而是通过阻塞队列通讯。生产者生产完数据后直接放入阻塞队列,无需等待消费者处理;消费者直接从阻塞队列取数据,无需寻找生产者。阻塞队列作为缓冲区,平衡了双方的处理能力。
4.3 基于Queue队列实现的生产者消费者模型
from multiprocessing import Queue,Process
# 生产者
def producer(q,name,food):
for i in range(3):
print(f'{name}生产了{food}{i}')
res = f'{food}{i}'
q.put(res)
# 消费者
def consumer(q,name):
while True:
res = q.get(timeout=5)
print(f'{name}吃了{res}')
if __name__ == '__main__':
q = Queue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
c1 = Process(target=consumer,args=(q,'Tom'))
p1.start()
c1.start()
此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。
解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。
4.4 改良版----生产者消费者模型
注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号
from multiprocessing import Queue,Process
def producer(q,name,food):
for i in range(3):
print(f'{name}生产了{food}{i}')
res = f'{food}{i}'
q.put(res)
q.put(None) # 当生产者结束生产的的时候,我们再队列的最后再做一个表示,告诉消费者,生产者已经不生产了,让消费者不要再去队列里拿东西了
def consumer(q,name):
while True:
res = q.get(timeout=5)
if res == None:break # 判断队列拿出的是不是生产者放的结束生产的标识,如果是则不取,直接退出,结束程序
print(f'{name}吃了{res}')
if __name__ == '__main__':
q = Queue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
c1 = Process(target=consumer,args=(q,'Tom'))
p1.start()
c1.start()
4.5 主进程在生产者生产结束以后,发送结束信号
使用这个方法的话,是很low的,有几个消费者就要在主进程中向队列中put几个结束信号
from multiprocessing import Queue,Process
import time,random
def producer(q,name,food):
for i in range(3):
print(f'{name}生产了{food}{i}')
time.sleep((random.randint(1,3)))
res = f'{food}{i}'
q.put(res)
# q.put(None) # 当生产者结束生产的的时候,我们再队列的最后再做一个表示,告诉消费者,生产者已经不生产了,让消费者不要再去队列里拿东西了
def consumer(q,name):
while True:
res = q.get(timeout=5)
if res == None:break # 判断队列拿出的是不是生产者放的结束生产的标识,如果是则不取,直接退出,结束程序
time.sleep((random.randint(1, 3)))
print(f'{name}吃了{res}')
if __name__ == '__main__':
q = Queue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
# 多个生产者进程
p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
p2 = Process(target=producer,args=(q,'xichen','冰激凌'))
p3 = Process(target=producer,args=(q,'喜陈','可乐'))
# 多个消费者进程
c1 = Process(target=consumer,args=(q,'Tom'))
c2 = Process(target=consumer,args=(q,'jack'))
# 告诉操作系统启动生产者进程
p1.start()
p2.start()
p3.start()
# 告诉操作系统启动消费者进程
c1.start()
c2.start()
p1.join()
p2.join()
p3.join()
q.put(None) # 几个消费者put几次
q.put(None)
JoinableQueue与高级应用
五、JoinableQueue方法
创建可连接的共享进程队列。这就像是一个Queue对象,但队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
5.1 方法介绍
JoinableQueue的实例p除了与Queue对象相同的方法之外,还具有以下方法:
q.task_done():使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。
q.join():生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。
5.2 joinableQueue队列实现生产者消费者模型
from multiprocessing import Queue,Process,JoinableQueue
import time,random
def producer(q,name,food):
for i in range(3):
print(f'{name}生产了{food}{i}')
# time.sleep((random.randint(1,3)))
res = f'{food}{i}'
q.put(res)
# q.put(None) # 当生产者结束生产的的时候,我们再队列的最后再做一个表示,告诉消费者,生产者已经不生产了,让消费者不要再去队列里拿东西了
q.join()
def consumer(q,name):
while True:
res = q.get(timeout=5)
# if res == None:break # 判断队列拿出的是不是生产者放的结束生产的标识,如果是则不取,直接退出,结束程序
# time.sleep((random.randint(1, 3)))
print(f'{name}吃了{res}')
q.task_done()#向q.join()发送一次信号,证明一个数据已经被取走了
if __name__ == '__main__':
q = JoinableQueue() # 为的是让生产者和消费者使用同一个队列,使用同一个队列进行通讯
# 多个生产者进程
p1 = Process(target=producer,args=(q,'Cecilia陈','巧克力'))
p2 = Process(target=producer,args=(q,'xichen','冰激凌'))
p3 = Process(target=producer,args=(q,'喜陈','可乐'))
# 多个消费者进程
c1 = Process(target=consumer,args=(q,'Tom'))
c2 = Process(target=consumer,args=(q,'jack'))
# 告诉操作系统启动生产者进程
p1.start()
p2.start()
p3.start()
# 把生产者设为守护进程
c1.daemon = True
c2.daemon = True
# 告诉操作系统启动消费者进程
c1.start()
c2.start()
p1.join()
p2.join()
p3.join() # 等待生产者生产完毕
print('主进程')
### 分析
# 生产者生产完毕--这是主进程最后一行代码结束--q.join()消费者已经取干净了,没有存在的意义了
# 这是主进程最后一行代码结束,消费者已经取干净了,没有存在的意义了.守护进程的概念.
5.3 测试joinableQueue
from multiprocessing import Process,Queue,JoinableQueue
q = JoinableQueue()
q.put('zhao') # 放队列里一个任务
q.put('qian')
print(q.get())
q.task_done() # 完成了一次任务
print(q.get())
q.task_done() # 完成了一次任务
q.join() #计数器不为0的时候 阻塞等待计数器为0后通过
# 想象成一个计数器 :put +1 task_done -1
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













