多进程
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 等方式来交换数据的。