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