一. 多进程
1. 要让python实现多进程,首先了解操作系统的知识。
Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(父进程)复制了一份(子进程)。然后分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID,这样做的理由是,一个父进程可以fork出很多子进程,所以父进程要记住每个子进程的ID,而子进程只要调用getppid() 就可以拿到父进程的ID。
python 的os模块封装了常见的操作系统调用,其中就包括fork(),就可以在python程序中创建子进程。
示例:
运行结果:
#!/bin/bash
import os
print 'process (%s) start...' %os.getpid()
pid = os.fork()
if pid == 0:
print 'I am child process (%s) and my parent is %s' %(os.getpid(),os.getppid())
else:
print 'I (%s) just create a child process (%s)' %(os.getpid(),pid)
运行结果:
[root@master python]# python demo8.py
process (27743) start...
I (27743) just create a child process (27744)
I am child process (27744) and my parent is 27743
有了fork调用,一个进程在接到新的任务时就会复制出一个子进程来处理新任务,常见的apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。
2. multiprocessing跨平台版本的多进程模块。
multiprocessing 模块提供了一个process类代表一个进程对象。下面的例子演示了启动一个子进程并等待结束。
运行结果;
运行结果:
运行结果:
#!/bin/bash
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 'process will start'
p.start()
p.join()
print 'Process end'
运行结果;
[root@master python]# python demo9.py
Parent process 28125
process will start
Run child process test (28126) ...
Process end
创建一个子进程,只需要传入一个执行函数和函数的参数,创建一个Process 实例,用start()方法启动, 这样 创建进程比fork() 还要简单。join() 方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
3. Pool进程池
如果需要大量启动子进程,可以使用进程池的方式批量创建子进程:
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 run %0.2f seconds' %(name,(end-start))
if __name__ == '__main__':
print 'Parent process %s' %os.getpid()
p = Pool()
for i in range(5):
p.apply_async(long_time_task,(i,))
print 'Waiting for all subprocesses done... '
p.close()
p.join()
print 'All subprocesses done.'
运行结果:
[root@master python]# python demo10.py
Parent process 28498
Waiting for all subprocesses done...
Run task 0 (28499) ...
Run task 1 (28500) ...
task 1 run 0.14 seconds
Run task 2 (28500) ...
task 2 run 0.43 seconds
Run task 3 (28500) ...
task 3 run 1.46 seconds
Run task 4 (28500) ...
task 4 run 0.58 seconds
task 0 run 2.79 seconds
All subprocesses done.
对Pool对象调用join() 方法会等待所有子进程执行完毕,调用join()之前必须先调用close(), 调用close() 之后就不能继续添加吸毒呢Process() 了。
对输出的结果,task1234是立即执行的,而task4 要等前面的某个task完成后才能执行,这是因为Pool的默认大小在我的电脑上是4,默认是CPU的核数。
p.apply_async() 是向进程池提交目标请求,join() 用来等待进程池中的worker进程执行完毕,防止主进程在worker进程结束前结束。
4. 进程间通信
python 的multiprocessing 模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
示例:在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue中读数据。
#!/bin/bash
from multiprocessing import Process, Queue
import os, time,random
def write(q):
for value in ['A','B','C']:
print 'put %s into queue.' %value
q.put(value)
time.sleep(random.random())
def read(q):
while True:
value = q.get(True)
print 'get %s from Queue...' %value
if __name__ == '__main__':
q = Queue()
pw = Process(target = write,args = (q,))
pr = Process(target = read,args = (q,))
pw.start()
pr.start()
pw.join()
pr.terminate()
运行结果:
put A into queue.
get A from Queue...
put B into queue.
get B from Queue...
put C into queue.
get C from Queue...
在Unix/Linux 下multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节,由于windows没有fork()调用,因此,父进程所有的python对象都必须通过pickle序列化再传到子进程去。
总结:
在Unix/Linux下,可以使用fork()
调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing
模块。
进程间通信是通过Queue
、Pipes
等实现的。
close() 跟terminate()的区别在于close()会等待池中的worker进程执行结束后再关闭pool,而terminate()是直接关闭。