Python高级编程-多进程

要让Python程序实现多进程(multiprocessing),咱们先了解操做系统的相关知识。python

Unix/Linux操做系统提供了一个fork()系统调用,它很是特殊。普通的函数调用,调用一次,返回一次,可是fork()调用一次,返回两次,由于操做系统自动把当前进程(称为父进程)复制了一份(称为子进程),而后,分别在父进程和子进程内返回。服务器

子进程永远返回0,而父进程返回子进程的ID。这样作的理由是,一个父进程能够fork出不少子进程,因此,父进程要记下每一个子进程的ID,而子进程只须要调用getppid()就能够拿到父进程的ID。多线程

Python的os模块封装了常见的系统调用,其中就包括fork,能够在Python程序中轻松建立子进程:app

 1 # /usr/bin/python
 2 import os
 3 import time
 4 
 5 
 6 def main():
 7         pid = os.fork() 
 8         if pid == 0: # 子进程中返回0
 9                 print("I am child process %d, my parent is %d" % (os.getpid(), os.getppid()))
10                 time.sleep(1) 
11         else: # 父进程中返回子进程id
12                 print("I %d just created child %d" % (os.getpid(), pid))
13                 time.sleep(1) # 防止父进程提早结束,子进程将由init进程接管,致使子进程中的os。getppid()输出的进程id是1
14 
15 if __name__ == '__main__':
16         main()

程序运行结果:async

1 I 20981 just created child 20982
2 I am child process 20982, my parent is 20981

因为Windows没有fork调用,上面的代码在Windows上没法运行。在Linux,Mac,Unix上均可以运行函数

有了fork调用,一个进程在接到新任务时就能够复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。spa

 

multiprocessing

若是你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。因为Windows没有fork调用,难道在Windows上没法用Python编写多进程的程序?操作系统

因为Python是跨平台的,天然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。线程

multiprocessing模块提供了一个Process类来表明一个进程对象,下面的例子演示了启动一个子进程并等待其结束:设计

(1)Process类:

 1 import multiprocessing
 2 import os
 3 import time
 4 
 5 
 6 # 子进程要执行的代码
 7 def run_process(i):
 8     print("%d Child %s process run" % (i, multiprocessing.current_process().name, ))
 9     time.sleep(1)
10     print("%d Child %s process end" % (i, multiprocessing.current_process().name,))
11 
12 
13 def main():
14     print("Process %d run" % (os.getpid()))
15     p1 = multiprocessing.Process(target=run_process, args=(1,)) # 和多线程Thread类建立实例类似
16     p1.start()
17     p1.join() # 主进程等待子进程结束
18     print("Process %d stop" % (os.getpid()))
19 
20 if __name__ == '__main__':
21     main()

执行结果以下:

1 Process 22324 run
2 1 Child Process-1 process run
3 1 Child Process-1 process end
4 Process 22324 stop

建立子进程时,只须要传入一个执行函数和函数的参数,建立一个Process实例,用start()方法启动,这样建立进程比fork()还要简单。

join()方法能够等待子进程结束后再继续往下运行,一般用于进程间的同步。

(2)Pool类:

在使用Python进行系统管理时,特别是同时操做多个文件目录或者远程控制多台主机,并行操做能够节约大量的时间。若是操做的对象数目不大时,还能够直接使用Process类动态的生成多个进程,十几个还好,可是若是上百个甚至更多,那手动去限制进程数量就显得特别的繁琐,此时Pool类就派上用场了。 
Pool类能够提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,若是池尚未满,就会建立一个新的进程来执行请求。若是池满,请求就会告知先等待,直到池中有进程结束,才会建立新的进程来执行这些请求。

 1 import multiprocessing
 2 import os
 3 import time
 4 
 5 
 6 def run_process(i):
 7     print("%d Child %s process run at %s" % (i, multiprocessing.current_process().name, time.time()))
 8     time.sleep(1)
 9     print("%d Child %s process end" % (i, multiprocessing.current_process().name,))
10 
11 
12 def main():
13     print("Process %d run" % (os.getpid()))
14     p2 = multiprocessing.Pool(multiprocessing.cpu_count())
15     for i in range(5):
16         p2.apply_async(run_process, args=(i,))  # 该函数用于启动进程,传递不定参数,主进程是非阻塞且支持结果返回进行回调。
17     p2.close()  # 关闭进程池(pool),使其不在接受新的任务。
18     p2.join()  # 主进程阻塞等待子进程的退出,join方法必须在close或terminate以后使用。
19     print("Process %d stop" % (os.getpid()))
20 
21 if __name__ == '__main__':
22     main()

运行结果:

 1 Process 29676 run
 2 0 Child SpawnPoolWorker-1 process run at 1487744098.910444
 3 1 Child SpawnPoolWorker-3 process run at 1487744098.931447
 4 2 Child SpawnPoolWorker-4 process run at 1487744098.936447
 5 3 Child SpawnPoolWorker-2 process run at 1487744098.96145
 6 0 Child SpawnPoolWorker-1 process end
 7 4 Child SpawnPoolWorker-1 process run at 1487744099.911545
 8 1 Child SpawnPoolWorker-3 process end
 9 2 Child SpawnPoolWorker-4 process end
10 3 Child SpawnPoolWorker-2 process end
11 4 Child SpawnPoolWorker-1 process end
12 Process 29676 stop

代码解读:

Pool对象调用join()方法会等待全部子进程执行完毕,调用join()以前必须先调用close(),调用close()以后就不能继续添加新的Process了。

请注意输出的结果,task 0123是马上执行的,而task 4要等待前面某个task完成后才执行,这是由于Pool的默认大小在个人电脑上是4,所以,最多同时执行4个进程。这是Pool有意设计的限制,并非操做系统的限制。若是改为:

 p2 = multiprocessing.Pool(5) 

就能够同时跑5个进程。

因为Pool的默认大小是CPU的核数,若是你的电脑拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。

pool类有一个map方法:  def map(self, func, iterable, chunksize=None) 与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果:

 1 def square(n):  # 计算平方值
 2     time.sleep(1)  #计算一次休眠1s
 3     print(n*n,time.time())
 4     return n*n
 5 
 6 
 7 def main():
 8     print("Process %d run" % (os.getpid()))
 9     number_list = [1, 2, 3, 4, 5, 6]
10     p2 = multiprocessing.Pool(multiprocessing.cpu_count())  # 本机4核CPU 
11     p2.map(square, number_list)
12     print("Process %d stop" % (os.getpid()))
13 
14 if __name__ == '__main__':
15     main()

运行结果:

1 Process 12264 run
2 1 1487744750.823629
3 4 1487744750.82863
4 9 1487744750.860633
5 16 1487744750.873634
6 25 1487744751.823729
7 36 1487744751.82873
8 Process 12264 stop

由于列表中共有6个元素,因为本机CPU有四核,在4个进程内的map方法同时能够对4个元素求平方,因此对于6个元素的列表,程序耗时2s。

因为map方法会使主进程阻塞,直到子进程返回,咱们并无调用p2.join(),主进程仍是等待子进程结束

相关文章
相关标签/搜索