文章目录

  • Queue
  • Pipe
  • Python标准库中对Pipe和Queue的定义


所谓进程,涵盖了一段程序执行过程中的所有内容。换言之,进程能够调用的内容,都在进程的内部,即无法调用进程外部的内容,所以就产生了进程间通信的需求。

multiprocessing中提供了两种通信通道,分别是QueuePipe

Queue

其中Queue的调用方式如下

from multiprocessing import Process, Queue
import time

def f(i, p, q):
    while 1:
        a = p.get()
        print(f"进程{i}收到一个{a}")
        time.sleep(0.5)
        q.put(a+1)

if __name__ == '__main__':
    q = Queue()
    p = Queue()
    Process(target=f, args=(1, q, p)).start()
    Process(target=f, args=(2, p, q)).start()
    p.put(1)

pq是两个Queue,在函数f中,一个用于收,一个用于发,主进程让p发出一个1后,进程2由于用p来接收,所以率先打印出内容,接收到了来自主进程的1,在等待0.5秒后,又用q发出一个2,而进程1则检测到了q发了这个2,由此将不断地进行下去。

E:\Documents\00\1005\cens>python test.py
进程2收到一个1
进程1收到一个2
进程2收到一个3
进程1收到一个4
...

如果两个进程都用一个Queue来进行收发会怎样呢?答案是怎么也不会怎么样,唯一可能存在的问题就是,当启动多个进程后,根本无法保证其中一个进程发送的内容会被谁接收到,例如下面这样写

def f(i, p):
    while 1:
        a = p.get()
        print(f"进程{i}收到一个{a}")
        time.sleep(0.5)
        p.put(a+1)
        

if __name__ == '__main__':
    q = Queue()
    p = Queue()
    Process(target=f, args=(1, p)).start()
    Process(target=f, args=(2, p)).start()
    Process(target=f, args=(3, p)).start()
    Process(target=f, args=(4, p)).start()
    p.put(1)

其输出为

进程1收到一个1
进程3收到一个2
进程4收到一个3
进程2收到一个4
...

Pipe

Pipe的用法和Queue基本一致,但其收发函数用到的是recvsend

from multiprocessing import Process, Pipe
import time

def f(i, p, q):
    while 1:
        a = p.recv()
        print(f"进程{i}收到一个{a}")
        time.sleep(0.5)
        p.send(a+1)
        
if __name__ == '__main__':
    p, q = Pipe()
    Process(target=f, args=(1, p, q)).start()
    Process(target=f, args=(2, q, p)).start()
    p.send(1)

其效果和Queue是一样的。

Python标准库中对Pipe和Queue的定义

multiprocessing.Pipe([duplex]) 返回一对 Connection 对象 (conn1, conn2) , 分别表示管道的两端。

如果 duplex 被置为 True (默认值),那么该管道是双向的。如果 duplex 被置为 False ,那么该管道是单向的,即 conn1 只能用于接收消息,而 conn2 仅能用于发送消息。

class multiprocessing.Queue([maxsize])
返回一个使用一个管道和少量锁和信号量实现的共享队列实例。当一个进程将一个对象放进队列中时,一个写入线程会启动并将对象从缓冲区写入管道中。

一旦超时,将抛出标准库 queue 模块中常见的异常 queue.Empty 和 queue.Full。

除了 task_done() 和 join() 之外,Queue 实现了标准库类 queue.Queue 中所有的方法。

qsize()
返回队列的大致长度。由于多线程或者多进程的上下文,这个数字是不可靠的。

请注意,这可能会在Unix平台上引起 NotImplementedError ,如 macOS ,因为其上没有实现 sem_getvalue() 。

empty()
如果队列是空的,返回 True ,反之返回 False 。 由于多线程或多进程的环境,该状态是不可靠的。

full()
如果队列是满的,返回 True ,反之返回 False 。 由于多线程或多进程的环境,该状态是不可靠的。

put(obj[, block[, timeout]])
将 obj 放入队列。如果可选参数 block 是 True (默认值) 而且 timeout 是 None (默认值), 将会阻塞当前进程,直到有空的缓冲槽。如果 timeout 是正数,将会在阻塞了最多 timeout 秒之后还是没有可用的缓冲槽时抛出 queue.Full 异常。反之 (block 是 False 时),仅当有可用缓冲槽时才放入对象,否则抛出 queue.Full 异常 (在这种情形下 timeout 参数会被忽略)。

在 3.8 版更改: 如果队列已经关闭,会抛出 ValueError 而不是 AssertionError 。

put_nowait(obj) 相当于 put(obj, False)。

get([block[, timeout]])

从队列中取出并返回对象。如果可选参数 block 是 True (默认值) 而且 timeout 是 None (默认值), 将会阻塞当前进程,直到队列中出现可用的对象。如果 timeout 是正数,将会在阻塞了最多 timeout 秒之后还是没有可用的对象时抛出 queue.Empty 异常。反之 (block 是 False 时),仅当有可用对象能够取出时返回,否则抛出 queue.Empty 异常 (在这种情形下 timeout 参数会被忽略)。

在 3.8 版更改: 如果队列已经关闭,会抛出 ValueError 而不是 OSError 。

get_nowait()
相当于 get(False) 。

multiprocessing.Queue 类有一些在 queue.Queue 类中没有出现的方法。这些方法在大多数情形下并不是必须的。

close()
指示当前进程将不会再往队列中放入对象。一旦所有缓冲区中的数据被写入管道之后,后台的线程会退出。这个方法在队列被gc回收时会自动调用。

join_thread()
等待后台线程。这个方法仅在调用了 close() 方法之后可用。这会阻塞当前进程,直到后台线程退出,确保所有缓冲区中的数据都被写入管道中。

默认情况下,如果一个不是队列创建者的进程试图退出,它会尝试等待这个队列的后台线程。这个进程可以使用 cancel_join_thread() 让 join_thread() 方法什么都不做直接跳过。

cancel_join_thread()
防止 join_thread() 方法阻塞当前进程。具体而言,这防止进程退出时自动等待后台线程退出。详见 join_thread()。

这个方法更好的名字可能是 allow_exit_without_flush()。 这可能会导致已排入队列的数据丢失,几乎可以肯定你将不需要用到这个方法。 实际上它仅适用于当你需要当前进程立即退出而不必等待将已排入的队列更新到下层管道,并且你不担心丢失数据的时候。

注解 该类的功能依赖于宿主操作系统具有可用的共享信号量实现。否则该类将被禁用,任何试图实例化一个 Queue 对象的操作都会抛出 ImportError 异常,更多信息详见 bpo-3770 。后续说明的任何专用队列对象亦如此。