在前几天的《python多进程与进程间通信:fork()方法和multiprocess实例》中,简明扼要地记录了些Python多进程的用法。
本文主要深入讲下pipe。主要是记录实践中遇到的问题:如何在多进程之间通过pipe()传输或共享一个实例对象;使用pipe()后为什么另一个进程会出来停滞现象而非并行执行;如何忽略其中一个进程的延迟,执行异步的并行计算。
pipe即一个管道,所创建管道返回两个连接对象,代表管道的两端,用于进程或者线程之间的通信。multiprocessing.Pipe()则可以双向通信,和队列Queue的单向通信不同。
我们看一个例子:
import multiprocessing
import os
def func(conn): #conn管道类型
conn.send(["a","b","c","d","e"]) #发送的数据
print("子进程",os.getpid(),conn.recv()) #收到的数据
conn.close() #关闭
if __name__=="__main__":
conn_a,conn_b=multiprocessing.Pipe() #创建一个管道,两个口
#print(id(conn_a),id(conn_b))
#print(type(conn_a), type(conn_b)) #multiprocessing.connection.PipeConnection类型
p=multiprocessing.Process(target=func,args=(conn_a,))
p.start()
conn_b.send([1,2,3,4,5])
print("主进程:",os.getpid(),conn_b.recv())
p.join()
p.terminate()
输出查看一下。
观察发现,conn_a发出的信息,是由conn_b接收到的,反之亦然。
这里,如果你试下不加p.terminate(),就会发现子进程没有关闭,再运行也一直是这个子进程。这样子进程会一直在后台耗费资源,建议在使用多进程时记得停止进程。
在使用pipe()时,发现管道另一端总是等待接收到数据,导致主进程停滞(可以用print输出看看目前进行到了哪一步)。然后分析是否是子进程的start()、join()的位置不对。关于这点请查阅:https://blog.csdn.net/zwzhzhzhzhzh/article/details/78996449。
如果没有问题的话,还有可能是管道recv()时卡住了。。我们先看下这段代码:
import numpy as np
import multiprocessing
import time
def funca(mylist):
time.sleep(1)
mylist.append(666.6)
def funcb(mylist):
time.sleep(1)
mylist.append(66.6)
if __name__ == "__main__":
samples = [1,2,3]
tic = time.time()
funca(samples)
funcb(samples)
toc = time.time()
print('pass time = ',toc-tic)
这段是坠基本的程序,由于人为设置了延迟,需要2s。若改写成双进程:
import numpy as np
import multiprocessing
import time
def funca(mylist):
time.sleep(1)
mylist.append(666.6)
def funcb(mylist):
time.sleep(1)
mylist.append(66.6)
if __name__ == "__main__":
samples = [1,2,3]
with multiprocessing.Manager() as MG: #重命名#
mylist = MG.list(samples) #主进程与子进程共享这个List
tic = time.time()
p1=multiprocessing.Process(target=funca,args=(mylist,) ) #创建新进程1
p2=multiprocessing.Process(target=funcb,args=(mylist,) ) #创建新进程2
p1.start()
p2.start()
p1.join()
p2.join()
p1.terminate()
p2.terminate()
toc = time.time()
print('pass time = ',toc-tic)
print(mylist)
那么就是1s!这个很好理解吧。。但是,如果引入pipe进行通信:
import numpy as np
import multiprocessing
import time
def funca(mylist,conn):
time.sleep(1)
mylist.append(666.6)
conn.send(['a','a','a'])
def funcb(mylist,conn):
time.sleep(1)
mylist.append(66.6)
conn.send(['b','b','b'])
if __name__ == "__main__":
samples = [1,2,3]
with multiprocessing.Manager() as MG: #重命名#
conn1,conn2 = multiprocessing.Pipe()
mylist = MG.list(samples) #主进程与子进程共享这个List
tic = time.time()
p1=multiprocessing.Process(target=funca,args=(mylist,conn1) ) #创建新进程1
print(conn2.recv())
p2=multiprocessing.Process(target=funcb,args=(mylist,conn1) ) #创建新进程2
print(conn2.recv())
p1.start()
p2.start()
p1.join()
p2.join()
p1.terminate()
p2.terminate()
print(list(mylist))
toc = time.time()
print('pass time = ',toc-tic)
ok。。你会发现没有结果输出。。程序卡住了。。。我们人为中断后,看看卡在了哪里:
管道一端一直在等着接收,因为p1没有启动。那么改一下:
程序可以跑通了,但是发现:
passtime是2s而不是并行时候的1s了。也就是说两个函数没有在并行执行。这是因为调用管道recv()方法的时候,主进程就一直在等收到信息,所以要等1s,收到后又调了一次函数,又等了1s。其实是串行的了。需要调整进程start()的位置!我们把主函数改成:
if __name__ == "__main__":
samples = [1,2,3]
with multiprocessing.Manager() as MG:
conn1,conn2 = multiprocessing.Pipe()
mylist = MG.list(samples)
tic = time.time()
p1=multiprocessing.Process(target=funca,args=(mylist,conn1) )
p1.start()
p2=multiprocessing.Process(target=funcb,args=(mylist,conn1) )
p2.start()
print(conn2.recv())
print(conn2.recv())
# p1.join()
# p2.join()
p1.terminate()
p2.terminate()
print(list(mylist))
toc = time.time()
print('pass time = ',toc-tic)
这样就是并行执行了,耗时是1s。
所以在写多进程的时候,要注意start()的位置。
但是,若funca和funcb耗时不同,例如funca耗时10s,funcb耗时1s,那么这段程序执行下来依然需要花费10s。这是因为调用了管道的recv()方法后,就会一直等待直到接收到信息为止。在某些情况下我们不需要等待子进程运行结束,比如子进程是负责更新某个数据的,每2s更新一次;而主进程需要取这个数据,但即使不是最新的数据也没有关系,这样并行执行起来速度以主进程速度为准。那么就不能使用pipe.recv()了,这种情况下建议使用mutiprocessing.Manager的list或Array来实现数据的共享。如果是你自定义的对象,可以加到list里面去作为元素,从而实现共享。