Python 中多进程模型 multiprocessing 在不同操作系统 api 不同,导致运行差异。本篇对其进行介绍。
介绍
导致差异的根本原因是多进程启动的方式
在 Linux 操作系统中,默认创建子进程的方式是 fork,而在 Mac/Windows 系统中,默认创建子进程的方式是 spawn。查看启动方式可以使用如下 Python 代码:
1 2 3 4
import multiprocessing
if __name__ == "__main__": print(multiprocessing.get_start_method())
在 Windows 上只能使用 spawn,但是,在 Linux 和 Mac(给予 Unix)上却可以设置采用哪种启动方式,方法如下:
1 2 3 4 5
import multiprocessing
if __name__ == "__main__": multiprocessing.set_start_method("fork") # "fork" or "spawn" 都可以在 Linux or Mac 上; Windows only have spawn print(multiprocessing.get_start_method())
Windows 系统只支持 spawn,而 Linux/Mac 既支持 spawn 又支持 fork,可根据需要选择。
其实,在 Linux/Mac 上启动方法有三类:
1 2 3 4
In [1]: import multiprocessing
In [2]: multiprocessing.get_all_start_methods() Out[2]: ['fork', 'spawn', 'forkserver']
各系统支持的方法:
Windows (win32): spawn
macOS (darwin): spawn, fork, forkserver
Linux (unix): spawn, fork, forkserver
各系统默认方法:
Windows (win32): spawn
macOS (darwin): spawn
Linux (unix): fork
三种区别:
spawn: start a new Python process.
fork: copy a Python process from an existing process.
forkserver: new process from which future forked processes will be copied.
Forking and spawning are two different start methods for new processes. Fork is the default on Linux (it isn’t available on Windows), while Windows and MacOS use spawn by default.
When a process is forked the child process inherits all the same variables in the same state as they were in the parent. Each child process then continues independently from the forking point. The pool divides the args between the children and they work though them sequentially.
On the other hand, when a process is spawned, it begins by starting a new Python interpreter. The current module is reimported and new versions of all the variables are created. The plot_function is then called on each of the the args allocated to that child process. As with forking, the child processes are independent of each other and the parent.
Neither method copies running threads into the child processes.
特点
fork
spawn
Import module at start of each child process
no
yes
Variables have same id as in parent process
yes
no
Child process gets variables defined in name == main block
yes
no
设置启动方法
方法一
1 2 3 4 5 6 7 8
import multiprocessing
...... # protect the entry point if __name__ == '__main__': # set the start method multiprocessing.set_start_method('spawn') ......
方法二
1 2 3 4 5 6 7 8 9 10 11 12 13
import multiprocessing
...... # protect the entry point if __name__ == '__main__': # get a context configured with a start method context = multiprocessing.get_context('fork') # create a child process via a context process = context.Process(...) ... # set the start method context.set_start_method('spawn', force=True) ......
QA
如果在 Windows 上或者采用 spawn 启动方式的 Linux/Mac 上,没有使用 if __name__ == "__main__":,那么会报异常,测试代码:
i=1,当前进程id:94918,当前进程处理模块名:__main__ 多进程启动方式:spawn 父进程id:94918 Traceback (most recent call last): File "<string>", line 1, in <module> File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 120, in spawn_main exitcode = _main(fd, parent_sentinel) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 129, in _main prepare(preparation_data) File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 240, in prepare _fixup_main_from_path(data['init_main_from_path']) File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 291, in _fixup_main_from_path main_content = runpy.run_path(main_path, ^^^^^^^^^^^^^^^^^^^^^^^^^ File "<frozen runpy>", line 291, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/Users/jinzhongxu/Codes/torch-test/windows-spawn.py", line 14, in <module> multiprocessing.set_start_method("spawn") File "/usr/local/miniconda/lib/python3.11/multiprocessing/context.py", line 247, in set_start_method raise RuntimeError('context has already been set') RuntimeError: context has already been set Traceback (most recent call last): File "<string>", line 1, in <module> File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 120, in spawn_main exitcode = _main(fd, parent_sentinel) ^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 129, in _main prepare(preparation_data) File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 240, in prepare _fixup_main_from_path(data['init_main_from_path']) File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 291, in _fixup_main_from_path main_content = runpy.run_path(main_path, ^^^^^^^^^^^^^^^^^^^^^^^^^ File "<frozen runpy>", line 291, in run_path File "<frozen runpy>", line 98, in _run_module_code File "<frozen runpy>", line 88, in _run_code File "/Users/jinzhongxu/Codes/torch-test/windows-spawn.py", line 14, in <module> multiprocessing.set_start_method("spawn") File "/usr/local/miniconda/lib/python3.11/multiprocessing/context.py", line 247, in set_start_method raise RuntimeError('context has already been set') RuntimeError: context has already been set i=1,当前进程id:94921,当前进程处理模块名:__mp_main__ i=1,当前进程id:94922,当前进程处理模块名:__mp_main__ i=1,当前进程id:94923,当前进程处理模块名:__mp_main__ i=1,当前进程id:94924,当前进程处理模块名:__mp_main__ Traceback (most recent call last): File "<string>", line 1, in <module> File "/usr/local/miniconda/lib/python3.11/multiprocessing/spawn.py", line 120, in spawn_main exitcode = _main(fd, parent_sentinel) ^^^^^^^^^^^^^^^^^^^^^^^^^^
或者在 Pytorch 多进程采样训练时,出现:freeze_support() 等报错。
这就是因为 spawn 方式会把当前脚本拷贝到多个子进程中,子进程有遇到启动多进程代码,会无限循环创建子进程执行。因此,最安全的方式是添加上: if __name__ == "__main__"。