引言
最近准备学习一下如何使用 Python 中的多进程在翻看相关书籍网上资料时发现所有代码都含有 if __name__=="__main__", 在实验的过程中发现如果在运行代码过程中, 没有这句话 Python 解释器就会报错虽然 Python 对于 multiprocessing 的文档第 17.2.1.1 节中 1 提到必须如此使用, 但是我觉得并没有根本上解释清楚因此我决定从源码来解释我的疑惑
- # 代码 0.1 错误代码
- import multiprocessing as mp
- import os
- def do():
- print("pid is : %s ..." % os.getpid())
- print("parent id is : %s ..." % os.getpid())
- p = mp.Process(target=do, args=())
- p.start()
- # 代码 0.2 正确代码
- import multiprocessing as mp
- import os
- def do():
- print("pid is : %s ..." % os.getpid())
- if __name__ == '__main__':
- print("parent id is : %s ..." % os.getpid())
- p = mp.Process(target=do, args=())
- p.start()
问题描述
问题
在运行代码 - 0.1 时, 会出现 RuntimeError, 错误提示如下但是运行代码 0.2 时就不会, 一切顺利
- An attempt has been made to start a new process before the current process has finished its bootstrapping phase.
- This probably means that you are not using fork to start your child processes and you have forgotten to use the
- proper idiom in the main module:
- if __name__ == '__main__':
- freeze_support()
- ...
- The "freeze\_support()" line can be omitted if the program is not going to be frozen to produce an executable.
问题产生的环境
运行环境: | Win10 |
IDE | Sublime Text3 |
简单解释
由于 Python 运行过程中, 新创建进程后, 进程会导入正在运行的文件, 即在运行代码 0.1 的时候, 代码在运行到 mp.Process 时, 新的进程会重新读入改代码, 对于没有 if __name__=="__main__" 保护的代码, 新进程都认为是要再次运行的代码, 这是子进程又一次运行 mp.Process, 但是在 multiprocessing.Process 的源码中是对子进程再次产生子进程是做了限制的, 是不允许的, 于是出现如上的错误提示
详细解释
先谈一谈 if__name__=="__main__"
在 Python 有关__main__的文档中 2 说明__main__是代码执行时的最高的命名空间(the name of the scope in which top-level code executes), 当代码被当做脚本读入的时候, 命名空间会被命名为__main__, 对于在脚本运行过程中读入的代码命名空间都不会被命名为__main__这也就是说创建的子进程是不会读取__name__=="__main__" 保护下的代码
再谈一谈 multiprocessing(win32 下的源码分析)
multiprocessing 根据平台不同会执行不同的代码: 在类 UNIX 系统下由于操作系统本身支持 fork()语句, win32 系统由于本身不支持 fork(), 因此在两种系统下 multiprocessing 会运行不同的代码, 如图 1 UNIX 平台图 2 win32 平台(包含在 context.py 文件中, Process 的定义也是在 context.py 文件中)
图 1 对于类 UNIX 系统平台
图 2 对于 win32 系统平台
Process 是一个高度依赖继承的类其父类是 BaseProcess, 如图 3 Process 类的定义在使用过程中先初始化一个 Process 实例, 然后通过 Process.start()来启动子进程我们继续看关于 Process.start()的定义, 如图 4 BaseProcess 类中的 start()定义在其中第 105 行, 调用了 self._Popen(self), 该函数重定义定义于 Process 类该函数按调用顺序最后会跳转到 popen_spwan_win32.py 文件中的 Popen 类, 如图 5 Popen 类的定义 Popen 类在初始话过程中首先调用 windows 相关接口, 生成一个管道(38 行), 取得对管道读写的句柄, 然后生成一个命令行的字符串列表 cmd, 如下:
- ['C:\\Program Files\\Python35\\python.exe', '-B', '-c',
- 'from multiprocessing.spawn import spawn_main;spawn_main(pipe_handle=928, parent_pid=9292)',
- '--multiprocessing-fork']
图 3 Process 类的定义
图 4 BaseProcess 类中的 start()定义
图 5 Popen 类的定义
这个字符串列表之后通过命令行送入系统, 得到相应的子进程和子线程的句柄及子进程和子线程的 ID 号在第 6566 行是通过 pickle 方法将必要的数据从父进程传输给子进程新进程是调用 spawn.py 文件中的 spawn_main 函数, 如图 6 spawnmain 的定义其中第 100 行的 steal_handle()的作用是子进程获取父进程生成的句柄, 用于后续通信使用 pickle 方法从父进程中读取必要数据而问题的出现就是在这之后出现! 该代码在第 106 行调用_main(), 其次在第 115 行调用 prepare(), 再次运行到 223 行时, 运行_fixup_main_from_name(), 而此时该函数会运行父进程的脚本因此对于没有 if__name__=="__main__" 保护的代码都是要运行的, 而此时在第二次运行 Process 创建新进程的时候在第 123 行 if getattr(process.current_process(), '_inheriting', False): 时, 由于子进程是具有_inheriting 属性, 因此会激发出上述错误代码
图 6 spawn_main 的定义
对于代码中生成新子进程时所用到的两个技术我觉很有趣也很苦恼, 因此决定继续看下去下面简单描述一下两个技术的概况
pickle 模块
首先是 pickle 模块 pickle 模块是一个 Python 中特有的数据格式, 与 JSON 等不同, 是不能被其他语言识别的格式在 Python 中的官方文档 3 中比较了 pickle 模块与 JSON 的不同之处, 以及介绍了 pickle 的使用条件简单摘录如下:
|
|
管道(Pipe)
这里理解管道是从类 UNIX 系统理解, 因为其理解起来更方便管道在类 UNIX 系统中也是一种文件, 在生成新的子进程的时候将两个进程都关联至同一个管道上, 这样就是双工通信 (父子进程相互可读可写) 如果要实现单工通信, 就关闭相应的通道(一方写一方读)4
参考文献
Python3.5 关于 multiprocessing 的文档 https://docs.python.org/3.5/library/multiprocessing.html
Python3.5 关于 \_\_main\_\_的文档 https://docs.python.org/3.5/library/__main__.html
Python3.5 关于 pickle 的文档 https://docs.python.org/3.5/library/pickle.html?highlight=pickle#module-pickle
网上一片关于 pipe 的介绍 http://www.cnblogs.com/qiaoyanlin/p/7576085.html
来源: https://www.cnblogs.com/wFrancow/p/8511711.html