多进程

Unix/Linux 系统提供了一个fork()的系统调用。这个调用比较特殊,普通的函数调用一次,返回一次。但是fork()调用一次,返回两次,因为操作系统自动把当前进程(父进程)复制了一份(子进程),然后分别在父进程和子进程中返回。

其中子进程中始终返回 0,而父进程中返回子进程的进程 ID。这是因为父进程可以通过返回的子进程 ID 来对子进程进行控制。Python 的os模块封装了这个系统调用,可以在 Python 中轻松的创建子进程。

但是需要注意的是,Windows 系统没有fork()调用,所以在 Windows 上是不能使用这个调用的。

Python 的multiprocess模块提供了跨平台的多进程功能,其中也包括了 Windows 的多进程支持。multiprocess模块中提供的 Process 类代表一个进程对象。以下示例演示了multiprocess模块的使用。

from multiprocess 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',))
	p.start()
	p.join()
	print('Child process end')

.start()方法可以启动 Process 实例,.join()可以等待子进程结束后继续执行。这里要注意的是由于 Windows 下没有fork(),所以 Python 需要模拟一个fork(),这就需要先使用pickle序列化所有对象再传递到子进程中,所以在 Windows 上出现子进程创建失败,要优先考虑pickle失败的情况。

要启动大量的子进程,可以使用进程池的方式批量创建子进程,这需要用到multiprocess模块提供的 Pool 类。以下示例演示了进程池的使用,读者可以自行试验其效果。

from multiprocess 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() * 10)
	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 subprocess done')
	p.close()
	p.join()
	print('All done')

对 Pool 对象调用.close()方法会等待所有子进程运行完毕,调用.join()之前需要先调用.close()结束向 Pool 中添加新的 Process。

前面提到过,父进程需要对子进程进行控制,这不仅包括启动和结束的控制,还包括输入和输出的控制。subprocess模块提供了启动一个子进程,之后控制其输入输出的能力。例如:

import subprocess

print('> nslookup www.baidu.com')
r = subprocess.call(['nslookup', 'www.baidu.com'])
print('Exit code %d' % r)

如果子进程需要输入,可以通过.communicate()方法输入。

进程间的通信是通过multiprocess模块提供的 Queue、Pipes 等方式来交换数据的。