Python实现多进程间通信的方式有很多种,例如队列,管道等。
  但是这些方式只适用于多个进程都是源于同一个父进程的情况。如果多个进程不是源于同一个父进程,只能用共享内存,信号量等方式,但是这些方式对于复杂的数据结构,例如Queue,dict,list等,使用起来比较麻烦,不够灵活。

1、进程间数据交换及共享

  不同进程间内存是不共享的,要想实现两个进程间的数据交换.multiprocessing 里面的Queue与 queue中的Queue 使用方法差不多。一个是进程中的队列函数,一个是队列库中的队列工具。

A、如何实现两个进程间的数据传递
第一步:单进程的队列工具的调用:(正常)

import queue,threading
def f(qq):
    qq.put("42,none,hello")

if __name__ =="__main__":
    q = queue.Queue()
    p = threading.Thread(target=f,args=(q,))
    p.start()
    print(q.get())
    p.join()
输出:
42,none,hello

第二步:父进程与子进程间的调用,数据共享会出错:(卡死

import queue,threading,multiprocessing
def f(qq):
    qq.put("42,none,hello")

if __name__ == "__main__":
    q = queue.Queue()
    p = multiprocessing.Process(target=f,args=(q,))
    p.start()
    print(q.get())

 第三步:要想实现两个不通进程间的数据交换,修改如下:

import threading
import multiprocessing
def f(qq):
    qq.put("42,none,hello")

if __name__ =="__main__":
    q = multiprocessing.Queue()
    p = multiprocessing.Process(target=f,args=(q,)) #相当于克隆一份数据。
    p.start()
    print(q.get())
    p.join()
输出:
42,none,hello

B、使用Pipes实现两个进程间的数据传递

   Pipe() 函数:返回一个双由管默认为双相连接的连接对象(双向)。包括其他的pipe实现,都只是两个进程之间的游玩,我给你,你来接收 或者是你来,我接收。 当然也可以做成双工的状态。类似于打电话,双方可以进行数据交换,可发送,可接收。

import multiprocessing,os

def f(conn):
    conn.send([42,None,"hello"]) #4、发送信息
    conn.send("子进程发送信息给父进程:我的pid号是 %s" % os.getpid()) #5、第二次发送信息
    print(conn.recv())#9、接收父进程发送的信息
    conn.close()

if __name__ == "__main__":
    parent_conn,child_conn = multiprocessing.Pipe() #1、做pipe管道
    p = multiprocessing.Process(target=f,args=(child_conn,))#2、编写进程,调用f函数,并传入child_conn值
    p.start() #3、启动进程
    print(parent_conn.recv()) #6、接收信息。只能接收一次,可以使用while True 进行循环接收。
    print(parent_conn.recv()) #7、接收第二次信息
    parent_conn.send("父进程发信息给子进程:hello ,还好吗? 我的ID号是 %s" % os.getpid()) #8、给子进行发送信息
    p.join()#10、等待执行完成,一次性打印

输出:
[42, None, 'hello']
子进程发送信息给父进程:我的pid号是 11151
父进程发信息给子进程:hello ,还好吗? 我的ID号是 11150

 注意:两连接()返回的对象代表管的两端。每个连接对象有send()和recv()方法。如果两个进程(或线程)试图同时读取或写入管道的同一端,则管道中的数据可能会损坏。当然,同时使用不同管端的过程不会有损坏的危险。

C、Manager  实现两个进程间的数据共享,也可以允许两个进程同时对内存中的数据修改。

Python通过Manager方式实现多个无关联进程共享数据。刚才A、B列举的两个例子,只能说的是实现了进程间的数据传递。如果要对两个进程间实现数据的共享,可以用使用Manager函数。

Python中进程间共享数据,处理基本的queue,pipe和value+array外,还提供了更高层次的封装。使用multiprocessing.Manager可以简单地使用这些高级接口。

Manager()返回的manager对象控制了一个server进程,此进程包含的python对象可以被其他的进程通过proxies来访问。从而达到多进程间数据通信且安全。

Manager支持的类型有list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array。

举例如下:

import  multiprocessing,os

def f(d,l):
    d[1] = '1'
    d['2'] = 2
    d[0.25] = None

    l.append(os.getpid())
    print(l)

if __name__ == "__main__":
    with multiprocessing.Manager() as manager:
        d = manager.dict() #生成一个字典,可在多个进程间共享和传递
        l = manager.list(range(5)) #生成一个列表,可在多个进程间共享和传递
        p_list = []
        for i in range(10):
            p = multiprocessing.Process(target=f,args=(d,l))
            p.start()
            p_list.append(p)
        for res in p_list: #等待结果一次性输出
            res.join()
            
        print(d)
        print(l)
输出:
[0, 1, 2, 3, 4, 11928]
[0, 1, 2, 3, 4, 11928, 11929]
[0, 1, 2, 3, 4, 11928, 11929, 11930]
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931]
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931, 11932]
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931, 11932, 11934, 11933]
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931, 11932, 11934, 11933]
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931, 11932, 11934, 11933, 11935, 11936]
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931, 11932, 11934, 11933, 11935, 11936]
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931, 11932, 11934, 11933, 11935, 11936, 11937]
{0.25: None, 1: '1', '2': 2}
[0, 1, 2, 3, 4, 11928, 11929, 11930, 11931, 11932, 11934, 11933, 11935, 11936, 11937]

插入知识点:多进程锁:multiprocess.Lock。进程同步:如果两个进程没有使用lock来同步,则他们对同一个文件的写操作可能会出现混乱。

from  multiprocessing import Process,Lock

def f(l,i):
    l.acquire()
    try:
        print('hello world',i)

    finally:
        l.release()

if __name__ == "__main__":
    lock = Lock()

    for num in range(10):
        Process(target=f,args=(lock,num)).start()
输出:
hello world 0
hello world 1
hello world 2
hello world 3
hello world 4
hello world 5
hello world 6
hello world 7
hello world 8
hello world 9

  注意:如果不加锁,输出的时候,可能就出现混乱。

 

2、进程池的使用

Python中 multiprocessing.Pool()函数:进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。 

进程池中有两个方法:

  • apply   串行执行
  • apply_async  并行执行
from multiprocessing import Process,Pool,freeze_support
import time,os

def Foo(i):
    time.sleep(2)
    print("in process",os.getpid())
    return i + 100

def Bar(arg):
    print('-->exec done:',arg,os.getpid())

if __name__ == "__main__":
    freeze_support()
    pool = Pool(processes=3)#允许进程同时放入3个进程
    print("主进程",os.getpid())
    for i in range(10): #以下三种形式,可以测试验证一下,每次进程池只能允许3个进程打印。
        pool.apply_async(func=Foo,args=(i,),callback=Bar) #回调到Bar函数
        #pool.apply(func=Foo,args=(i,)) 
        #pool.apply_async(func=Foo, args=(i,))
    print('end')
    pool.close()
    pool.join() #进程池中进程执行完毕后在关闭,如果注释,那么程序会直接关闭。
输出:
主进程 13385
end
in process 13388
in process 13387
in process 13386
-->exec done: 101 13385
-->exec done: 100 13385
-->exec done: 102 13385
in process 13386
in process 13388
in process 13387
-->exec done: 104 13385
-->exec done: 105 13385
-->exec done: 103 13385
in process 13386
in process 13387
in process 13388
-->exec done: 107 13385
-->exec done: 108 13385
-->exec done: 106 13385
in process 13387
-->exec done: 109 13385