python多进程:如何忽视子进程延迟执行并行计算——multiprocessing.Pipe()详解

在前几天的《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里面去作为元素,从而实现共享。

发布了150 篇原创文章 · 获赞 334 · 访问量 74万+

猜你喜欢

转载自blog.csdn.net/lyxleft/article/details/88902084