在python中,如果要做多任务并行的编程,必须要掌握multiprocessing
库的相关运用。在python的multiprocessing官方文档中,已然详细给出了multiprocessing
库的相关用法。多进程编程其实还是有很多坑存在的,为了进一步探索python多进程的机制,提升对python多进程编程的理解,本篇文章会对多进程模块的实现进行一次详细的剖析。
多进程编程的第一话,首先来聊聊一个新的python子进程是如何诞生的。
首先我们需要了解这么一个事情,python创建的进程之间模块状态是相互隔离的。在多进程的场景下,代码中定义的各种变量,其值并不一定会共享。我们举个例子:
1 | import multiprocessing as mp |
在函数test_module_isolation
中,首先是调用mp_module.init_flag()
改变了mp_module
模块一个变量的值,之后同时在主进程和新起的进程调用了_test_module_isolation()
去打印mp_module
中的变量。结果如下:
1 | main process pid: 14836 |
可以看到,主进程(14836)中打印出来的变量是初始化过的,而子进程(22212)打印出来的变量是没有经过初始化的。
通过Process
实例创建出来的进程对象,调用start
方法即可启动新进程,执行target
对应的方法。
1 | # BaseProcess.start |
创建进程的方式有多种,其中spawn
模式为windows/linux系统下均兼容的,且为windows的默认模式。在Process._Popen
中,会通过_default_context.get_context()
获取当前的进程启动模式。
以windows下的spawn
模式为例,我们看下进程启动的代码的实现。主要逻辑分布在3个地方:
popen_spawn_win32.Popen
multiprocessing/spawn.py
process.BaseProcess
1 | # popen_fork.Popen |
这里经历了以下几个步骤:
- 用
_winapi.CreatePipe
创建一对用于进程间通信的handle
rhandle
会传给子进程,whandle
会被主进程用于给子进程发送数据
- 获取构建子进程运行时环境所需要的准备数据
prep_data
- 包括
sys.argv
、sys.path
、日志配置、初始化__main__
模块的文件路径等
- 包括
- 通过
spawn.get_command_line
初始化spawn
进程所需的命令- 从函数定义中可以看到,子进程执行的命令中会调用
spawn.spawn_main
函数
- 从函数定义中可以看到,子进程执行的命令中会调用
- 调用
_winapi.CreateProcess
生成子进程 - 通过
whandle
转换后的fd向子进程写入序列化后的准备数据以及自身Process
实例数据 - 子进程在
spawn_main
函数将传入的rhandle
转化为fd,在_main
函数中接收主进程传入的数据,并执行对应操作- 接收
prep_data
,初始化子进程运行时环境。其中会重新执行原来__main__
模块对应的代码块- 这里注意,子进程执行原来
__main__
模块的代码块,是以__mp_main__
这个模块名执行的。因此,原来代码里头if __name__ == '__main__'
下面的都执行不到。所以,子进程跑不到mp_module.init_flag()
,里面的flag
值当然没有被初始化。如果要让flag
被初始化的话,相信聪明的你知道怎么做。
- 这里注意,子进程执行原来
- 执行
Process
实例的_bootstrap
函数。_bootstrap
函数最终调用run
函数,执行_target
对应的内容
- 接收
这样,新的python进程就被创建并开始运行了。