BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / python / #26055同步于 2022/3/6
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Python机器人发帖

【问题】多进程spawn的一个问题

isla
2022/3/6镜像同步11 回复
spawn的python官方文档部分: spawn The parent process starts a fresh python interpreter process. The child process will only inherit those resources necessary to run the process object’s run() method. In particular, unnecessary file descriptors and handles from the parent process will not be inherited. Starting a process using this method is rather slow compared to using fork or forkserver. Available on Unix and Windows. The default on Windows and macOS. 那么问题来了,spawn只继承运行process对象run方法的必要资源。这个必要资源定义是个什么,按照mutiprocessing的源代码(位于基类BaseProcess,子类SpawnProcess只重写了开进程的_Popen方法): ``` python def run(self): ''' Method to be run in sub-process; can be overridden in sub-class ''' if self._target: self._target(*self._args, **self._kwargs) ``` 也就是说运行run需要的就是建立Process对象(实际就是SpawnProcess)时候给构造函数传入的target, args, kwargs。但是经过我自己的实验(实际上是有一个spawn子进程对主进程os.environ的修改需求): ``` python def f(): print(os.environ) def start_process(): global f os.environ["A_NEW_ADDED"] = "821" mp_context = mp.get_context("spawn") for i in range(0, 2): process = mp_context.Process(target=f, args=()) process.start() if __name__ == "__main__": start_process() ``` 上面代码spawn了两个进程,子进程print的os.environ实际上是有新加入的键值对"A_NEW_ADDED": "821"的。所以我就在困惑了,这个「继承必要的资源」中究竟哪些是必要的? 求熟悉python和底层的大佬给个解释,_Popen方法中有个CreateProcess函数,因为_winapi模块貌似是c/cpp编译过的dylib了所以已经看不到具体定义了,往下找不动了
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
isla机器人#1 · 2022/3/10
zd
Vampire机器人#2 · 2022/3/14
在 Linux 上用 strace 随便看了一眼(Python 3.10.2),子进程启动调了 vfork,还有一些描述符设置了 O_CLOEXEC。具体干了什么建议直接看 CPython 源码。
isla机器人#3 · 2022/3/15
nb 感谢提供线索 好久前看cpython源代码没看懂给整自闭了 这两天看看找个好方法看一下吧 感谢! 【 在 Vampire 的大作中提到: 】 : 在 Linux 上用 strace 随便看了一眼(Python 3.10.2),子进程启动调了 vfork,还有一些描述符设置了 O_CLOEXEC。具体干了什么建议直接看 CPython 源码。 : --
nitroethane机器人#4 · 2022/3/15
之前遇到过这个问题。spawn 模式应该是用 execve 系统调用新起个进程,然后把类的信息通过 pickle 标准库传过去,所以本地变量之类的数据在新起的进程中是没有的。而 fork 模式是用 fork 系统调用创建个新进程,本地变量之类的是有的
isla机器人#5 · 2022/3/15
诶 那很奇怪 这么说的话 spawn模式下主进程的os.environ不应该传过去吧 但我实验中发现这个新加入的环境变量也一并传过去了 当然我是在Windows下测试的 Linux没测过 【 在 nitroethane 的大作中提到: 】 : 之前遇到过这个问题。spawn 模式应该是用 execve 系统调用新起个进程,然后把类的信息通过 pickle 标准库传过去,所以本地变量之类的数据在新起的进程中是没有的。而 fork 模式是用 fork 系统调用创建个新进程,本地变量之类的是有的
nitroethane机器人#6 · 2022/3/15
传环境变量没什么问题啊。你觉着不应该的理由是什么。 大概看了下源码,spawn 模式下調用 Process 对象的 start 方法启动子进程时调用的是[Popen](https://github.com/python/cpython/blob/0e4bebad0a099e5f1440edcdd0ce3eae9952bf74/Lib/multiprocessing/popen_spawn_posix.py#L26) 对象的 `_launch` 方法。 `_launch` 方法中调用 [`spawn.get_command_line`](https://github.com/python/cpython/blob/0e4bebad0a099e5f1440edcdd0ce3eae9952bf74/Lib/multiprocessing/popen_spawn_posix.py#L55) 函数拼接用于创建子进程的命令。在这个函数中可以看到,子进程是通过执行 python 命令加 `-c` 参数执行 python 代码进行创建的: ``` def get_command_line(**kwds): ''' Returns prefix of command line used for spawning a child process ''' if getattr(sys, 'frozen', False): return ([sys.executable, '--multiprocessing-fork'] + ['%s=%r' % item for item in kwds.items()]) else: prog = 'from multiprocessing.spawn import spawn_main; spawn_main(%s)' prog %= ', '.join('%s=%r' % item for item in kwds.items()) opts = util._args_from_interpreter_flags() return [_python_exe] + opts + ['-c', prog, '--multiprocessing-fork'] ``` 接下来就是调用 `spawnv_passfds` [函数](https://github.com/python/cpython/blob/0e4bebad0a099e5f1440edcdd0ce3eae9952bf74/Lib/multiprocessing/util.py#L447)创建子进程。 ``` def spawnv_passfds(path, args, passfds): import _posixsubprocess passfds = tuple(sorted(map(int, passfds))) errpipe_read, errpipe_write = os.pipe() try: return _posixsubprocess.fork_exec( args, [os.fsencode(path)], True, passfds, None, None, -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write, False, False, None, None, None, -1, None) finally: os.close(errpipe_read) os.close(errpipe_write) ``` 其中 `_posixsubprocess` 模块是用 C 写的(这里以 CPython 为例),`fork_exec` 函数的[代码在这里](https://github.com/python/cpython/blob/6dfe09fc5fd5a3ddc6009d5656e635eae30c5240/Modules/_posixsubprocess.c#L1054)。跟踪下去,最终创建子进程是在 `child_exec` 函数中。因为最开始给 envp 参数传的是 NULL,因此最后是调用 C 库函数 [`execv` 创建子进程](https://github.com/python/cpython/blob/6dfe09fc5fd5a3ddc6009d5656e635eae30c5240/Modules/_posixsubprocess.c#L609)。看 glibc [源码可知](https://code.woboq.org/userspace/glibc/posix/execv.c.html),execv 函数直接使用 `__environ` 变量,因此子进程的环境变量和父进程的是一致的(因为是先 fork 了)。 【 在 isla 的大作中提到: 】 :诶 那很奇怪 这么说的话 spawn模式下主进程的os.environ不应该传过去吧 但我实验中发现这个新加入的环境变量也一并传过去了 :当然我是在Windows下测试的 Linux没测过
nitroethane机器人#7 · 2022/3/15
windows 的情况应该也差不多。不太熟悉 Windows,就不分析了 【 在 nitroethane 的大作中提到: 】 : [md] : 传环境变量没什么问题啊。你觉着不应该的理由是什么。 : 大概看了下源码,spawn 模式下調用 Process 对象的 start 方法启动子进程时调用的是[Popen](https://github.com/python/cpython/blob/0e4bebad0a099e5f1440edcdd0ce3eae9952bf74/Lib/multiprocessing/popen_spawn_posix.py#L26) 对象的 `_launch` 方法。 : ...................
isla机器人#8 · 2022/3/16
太详细了 感谢 我的点是 官方文档说明是 spawn只继承运行run方法所需的资源 stackoverflow上我查到子进程是新的Python解释器(这个与您发的get_command_line一致)所以我认为主进程os.environ其实并不是必须资源 子进程启动Python解释器可以直接拿到新的环境变量副本而不需要从主进程复制 但实际运行结果并非如此 所以我就想知道所谓的"运行run方法所需的资源"这个描述具体是指哪些对象 (另一方面就是我看源码用vscode定位经常有跳转问题,有时候会跳转到pyi去) 现在理解了 还是得看源码 感谢! 【 在 nitroethane 的大作中提到: 】 : [md] : 传环境变量没什么问题啊。你觉着不应该的理由是什么。 : ............
nitroethane机器人#9 · 2022/3/16
如果你了解 Linux 系统编程的话这个问题就很好理解。glibc 的运行时在调用 main 函数前会设置环境变量,全局变量__environ 指向环境变量的开头。os.environ 里的环境变量数据实际来自于解释器刚启动时通过 getenv 库函数获取并设置的。你往里添加新的变量时,实际是调用 putenv 库函数将环境变量添加到 __environ 变量所指的数组里。 所以 os.environ 算是必须资源吧 【 在 isla 的大作中提到: 】 :太详细了 感谢 :我的点是 官方文档说明是 spawn只继承运行run方法所需的资源 stackoverflow上我查到子进程是新的Python解释器(这个与您发的get_command_line一致)所以我认为主进程os.environ其实并不是必须资源 子进程启动Python解释器可以直接拿到新的环境变量副本而不需要从主进程复制 但实际运行结果并非如此 所以我就想知道所谓的"运行run方法所需的资源"这个描述具体是指哪些对象 :(另一方面就是我看源码用vscode定位经常有跳转问题,有时候会跳转到pyi去) :现在理解了 还是得看源码 感谢!