python多进程与进程间通信:fork()⽅法和multiprocess实
例
⽬录
考虑到现在电脑⼀般都有双核甚⾄四核的CPU,故可设计算法并⾏的⽅法,通过python编程实现并⾏运算,从⽽加快处理的速度。虽然没有GPU进⾏图像算法的并⾏运算,但是能利⽤好双核CPU应该也是⼤有帮助吧!
fork()⽅法(windows不可⽤)
对于Linux/Unix/MacOS系统,可以⽤fork()⽅法创建⼦进程。如果你是这些系统,或macbook电脑,就学习fork()⽅法即可。
由于没有实践过,这部分可参考,但是⽹上的资料都⽐较简练,需要深⼊了解实践的话还需要多研究⼀下。
由于Windows没有fork调⽤,上⾯的代码在Windows上⽆法运⾏。由于Mac系统是基于BSD(Unix的⼀种)内核,所以,在Mac下运⾏是没有问题的。有了fork调⽤,⼀个进程在接到新任务时就可以复制出⼀
个⼦进程来处理新任务,在上述例⼦⾥,先fork()好了⼦进程,遇到if...语句后,⽗进程处理了第⼀个语句(if),打印相应内容;接着else:则由⼦进程来处理。(常见的Apache服务器就是由⽗进程监听端⼝,每当有新的http请求时,就fork出⼦进程来处理新的http请求。)
关于fork()⽅法,需要注意的⼏个关键点:
1、⼦进程与⽗进程的复制关系:
⼦进程拥有⽗进程的所有内存的精确副本。当使⽤fork给进程分叉时,它会创建⼀个⾃⼰的副本。在多线程环境中,fork意味着执⾏的线程是重复的,但是可以分开。因此,有的学者认为fork很像⽣物⾥克隆的概念。⼦进程克隆了⽗进程。获取了⽗进程的数据和代码。
⼦进程退出必须使⽤os.exit(0),否则⼦进程将返回到⽗进程中。
2、相互独⽴关系
⽗进程和⼦进程的执⾏是相互独⽴的。fork操作为⼦进程创建了⼀个单独的地址空间。⼦进程从操作系统接收⼀个新的进程号PID号(PID,进程标识符)。
3、fork()的返回值:
廖雪峰pythonfork的返回值决定当前正在进⾏的是哪⼀个进程:0表⽰在⼦进程中,正值表⽰在⽗进程中,-1表⽰出错了。
跨平台multiprocessing
虽然windows不⽀持fork⽅法,但是鉴于python的跨平台特性,windows也是有办法的——multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing模块提供了⼀个Process类来代表⼀个进程对象。
下⾯的例⼦演⽰了启动⼀个⼦进程并等待其结束:
from multiprocessing import Process
import os
# ⼦进程要执⾏的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
运⾏结果:
创建⼦进程时,⽤process函数,传⼊参数为:⼀个要执⾏的⽅法、函数的参数,创建⼀个Process实例,⽤start()⽅法启动,这样创建进程⽐fork()还要简单。join()⽅法可以等待⼦进程结束后再继续往下运⾏,通常⽤于进程间的同步,是针对每⼀个进程单独起作⽤的,这个和setdaemon不同。
进程池Pool
如果要启动⼤量的⼦进程,可以⽤进程池的⽅式批量创建⼦进程。先看下源码:
processes参数代表了进程池数⽬,默认是cpu核数。多进程的启动⽅法是apply_async()。
func是我们要启动的函数,args和kwds是可变参数。其他的不重要。。
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all ')
p.close()
p.join()
print('All subprocesses done.')
对Pool对象调⽤join()⽅法会等待所有⼦进程执⾏完毕,调⽤join()之前必须先调⽤close(),调⽤close()之后就不能继续添加新的Process了。
请注意输出的结果,task 0,1,2,3是⽴刻执⾏的,⽽task 4要等待前⾯某个task完成后才执⾏,这是因为Pool的默认⼤⼩在我的电脑上是4,因此,最多同时执⾏4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:
p = Pool(5)
就可以同时跑5个进程。
由于Pool的默认⼤⼩是CPU的核数,如果你不幸拥有8核CPU,你要提交⾄少9个⼦进程才能看到上⾯的等待效果。
进程间通信
Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种⽅式来交换数据。
Queue
我们以Queue为例,在⽗进程中创建两个⼦进程,⼀个往Queue⾥写数据,⼀个从Queue⾥读数据:
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执⾏的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s ' % value)
q.put(value)
time.sleep(random.random())
# 读数据进程执⾏的代码:
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__=='__main__':
# ⽗进程创建Queue,并传给各个⼦进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动⼦进程pw,写⼊:
pw.start()
# 启动⼦进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程⾥是死循环,⽆法等待其结束,只能强⾏终⽌:
在Unix/Linux下,multiprocessing模块封装了fork()调⽤,使我们不需要关注fork()的细节。由于Windows没有fork调⽤,因
此,multiprocessing需要“模拟”出fork的效果,⽗进程所有Python对象都必须通过pickle序列化再传到⼦进程去,所有,如
果multiprocessing在Windows下调⽤失败了,要先考虑是不是pickle失败了。
多进程中,对于⼀个变量,每个进程都是复制了⼀份,所以每个进程之间修改数据互不影响。 Queue()⽅法相当于第三⽅,把进程A 的数据序列化后传给进程B反序列化得到数据。并不是⼀个共享的变量。⽽是实现了数据的传递。
pipe()
类似于socket ⼀端发送,⼀端接收,实现通信。
from multiprocessing import Process,Pipe
def f(conn):
conn.send([5,'hello'])
conn.close()
if __name__ =='__main__':
parent_conn,child_conn = Pipe()
p = Process(target=f,args=(child_conn,))
p.start()
print(v())
p.join()
或⽤pipe()实现双⽅相互通信:
def f(conn):
conn.send([5,'hello']) #发送数据
v()) #接收数据
conn.close()
if __name__ =='__main__':
parent_conn,child_conn = Pipe()
p = Process(target=f,args=(child_conn,))
p.start()
print(v()) #接收数据
parent_conn.send("hehe你好") #发送数据
p.join()
Manager
个⼈认为manager是最简单易懂的⽅法,⾃⼰也是⽤它来⽅便地实现并⾏计算。
由manager()返回的manager对象控制⼀个包含Python对象的服务器进程,并允许其他进程使⽤代理来操作它们。
由manager()返回的管理器将⽀持类型列表、命令、名称空间、锁、RLock、信号量、BoundedSemaphore、Condition、Event、Barrier、Queue、Value和Array。在基本的⼀些编程案例中,其实只是需要上述类型的基本数据的传递、互通,所以⽤manager 很⽅便。下⾯是我的⼀个例⼦:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论