「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」。
在 《【Python】多进程-进程间共享对象》 中,讲解了如何通过共享内存和服务进程,使不同的进程能够访问同一变量。不同的进程也可以通过这一变量交换信息,完成通信。在使用共享内存时,我们需要事先指明共享变量的类型。本文将介绍使用队列和管道实现进程之间的通信,与使用共享内存相比,使用队列和管道进行通信更加灵活。
本文将介绍使用队列实现多进程之间的通信,并在下篇文件介绍使用管道实现多进程之间的通信。
queue.Queue
与 multiprocessing.Queue
Python 的 queue.Queue(maxsize=0)
中,实现了一个同步普通队列。在存取元素时,遵循先进先出的规制。如果队列为空,则取元素的进程会被阻塞;如果队列已满,则放元素的进程会被阻塞。
multiprocessing
库也提供了 multiprocessing.Queue()
,除了 queue.Queue()
中具有的方法,multiprocessing.Queue()
还提供了一些进程相关的方法。
下面,我们对比一下这两个队列:
共同的方法
下面这些方法,queue.Queue
和 multiprocessing.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
用于控制队列为空时,取元素的进程是否被阻塞,block
和timeout
的含义与 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()