【Python】多进程-进程间通信之队列

1,004 阅读4分钟

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」。

在 《【Python】多进程-进程间共享对象》 中,讲解了如何通过共享内存和服务进程,使不同的进程能够访问同一变量。不同的进程也可以通过这一变量交换信息,完成通信。在使用共享内存时,我们需要事先指明共享变量的类型。本文将介绍使用队列和管道实现进程之间的通信,与使用共享内存相比,使用队列和管道进行通信更加灵活。

本文将介绍使用队列实现多进程之间的通信,并在下篇文件介绍使用管道实现多进程之间的通信。

queue.Queuemultiprocessing.Queue

Python 的 queue.Queue(maxsize=0) 中,实现了一个同步普通队列。在存取元素时,遵循先进先出的规制。如果队列为空,则取元素的进程会被阻塞;如果队列已满,则放元素的进程会被阻塞。

multiprocessing 库也提供了 multiprocessing.Queue(),除了 queue.Queue() 中具有的方法,multiprocessing.Queue() 还提供了一些进程相关的方法。

下面,我们对比一下这两个队列:

共同的方法

下面这些方法,queue.Queuemultiprocessing.Queue 都有,而且参数也相同:

qsize()可以获取队列的大致大小,但是由于得到返回值后队列的内容可能已经被其他进程修改,因此不能根据该返回值判断之后的操作会不会被阻塞。

full()empty() 分别用于判断队列是否是满/空的,当队列满/空时返回 True,否则返回 False,与 qsize() 一样,不能根据返回值判断之后的操作会不会被阻塞

使用 put(item, block=True, timeout=None) 可以将元素放入队列队尾,block 用于控制队列已满时,放元素的进程是否要被阻塞,默认为 True(被阻塞);timeout 用于控制被阻塞的时间,超时则会抛出异常。如果不希望进程被阻塞,也可以使用 put_nowait(item) 方法,该方法相当于put(item, False) 

使用 get(block=True, timeout=None)可以将队头元素从队列取出,同样地,block 用于控制队列为空时,取元素的进程是否被阻塞,blocktimeout的含义与 put 相同。如果不想进程被阻塞,可以使用get_nowait() 方法,相当于 get(False) 。

queue.Queue 特有的方法

下面这些方法,只有 queue.Queue 具有:

task_done() 被队列的消费者线程使用,用于表示前面排队的任务已经被完成。进程在调用 Queue.get() 后,通过调用 task_done() 告诉队列这个操作已经完成。但如果调用次数超过被放入队列的元素数量,会抛出 ValueError 异常。

当向队列添加元素时,未完成任务计数增加;当调用 task_done(),未完成任务计数减少。当未完成任务计数减少到0时,阻塞解除。join() 方法会阻塞直到队列中所有的元素都被接收和处理完毕。

multiprocessing.Queue 特有的方法

multiprocessing.Queue 提供了一些进程相关的方法:

进程通过调用 close() 方法,指示当前进程将不会再往队列中放入对象。这个方法在队列被垃圾回收时会自动调用。

join_thread() 方法会阻塞当前进程,等待后台线程,直到后台线程退出,以确保所有缓冲区中的数据都被写入队列。这个方法仅在调用了 close() 方法之后依然可用。默认情况下,如果一个不是队列创建者的进程试图退出,它会尝试等待这个队列的后台线程。cancel_join_thread()方法则可以阻止  join_thread() 方法阻塞当前进程,即防止进程退出时自动等待后台线程退出。

代码

最后,通过代码演示一下多进程通过队列进行通信。

代码实现了一个经典的生产者-消费者问题:一个厨师生产蛋糕,三个顾客嗷嗷待哺:

import multiprocessing
import time 
import random

class cake:
    def __init__(self, time):
        self.productionTime = time 
    
    def eat(self):
        print("Eat a cake product at ", self.productionTime)
    
def cook(q):
    for _ in range(6):
        q.put(cake(time.time()))
        time.sleep(random.random())
    q.close()

def customer(q):
    for _ in range(2):
        cake = q.get()
        cake.eat()
        time.sleep(random.random() * 5)

if __name__ == "__main__":

    q = multiprocessing.Queue(maxsize= 5)

    p1 = multiprocessing.Process(target= cook, args=(q, ))
    p2 = multiprocessing.Process(target= customer, args=(q, ))
    p3 = multiprocessing.Process(target= customer, args=(q, ))
    p4 = multiprocessing.Process(target= customer, args=(q, ))

    p1.start()
    p2.start()
    p3.start()
    p4.start()