使用 subprocess 模块是不是会释放 python GIL?

Posted

技术标签:

【中文标题】使用 subprocess 模块是不是会释放 python GIL?【英文标题】:Does using the subprocess module release the python GIL?使用 subprocess 模块是否会释放 python GIL? 【发布时间】:2014-06-15 15:37:14 【问题描述】:

当通过 Python 的subprocess 模块调用需要较长时间的 linux 二进制文件时,是否会释放 GIL?

我想并行化一些从命令行调用二进制程序的代码。使用线程(通过threadingmultiprocessing.pool.ThreadPool)还是multiprocessing 更好?我的假设是,如果 subprocess 发布 GIL,那么选择 threading 选项会更好。

【问题讨论】:

请澄清。当前的答案认为您担心子进程本身以某种方式持有 GIL,但我认为您可能担心 subprocess.call()subprocess.Popen(...).wait() 会阻塞调用者中的其他线程。 (他们没有。) @pilcrow:作为从 Google 遇到这个问题的人,我建议您将您的评论变成答案,因为与现有答案不同,它解决了实际问题。 @Rörd:我现在已经这样做了,谢谢。 【参考方案1】:

当通过 Python 的subprocess 模块调用需要较长时间的 linux 二进制文件时,是否会释放 GIL?

是的,它在调用过程中释放Global Interpreter Lock (GIL)。

您可能知道,在 POSIX 平台上,subprocessforkexecvewaitpid 的“原始”组件之上提供了便利的接口。

通过检查 CPython 2.7.9 源代码,forkexecve发布 GIL。但是,这些调用不会阻塞,所以我们不希望 GIL 被释放。

waitpid 当然确实阻塞,但我们看到它的实现确实放弃了使用 ALLOW_THREADS 宏的 GIL:

static PyObject *
posix_waitpid(PyObject *self, PyObject *args)

....
Py_BEGIN_ALLOW_THREADS
pid = waitpid(pid, &status, options);
Py_END_ALLOW_THREADS
....

这也可以通过从演示多线程 python 脚本调用一些长时间运行的程序来测试,例如 sleep

【讨论】:

根据经验,CPython 在使用阻塞 OS API(例如 waitpid())时发布 GIL。 subprocess 模块中的方法没有任何具体内容。注意:execve() obviosly 阻塞(在这种情况下,在 fork 之后的 in the child)- 它仅在错误时返回。 fork() 是一个特例:"read this discussion to understand why you should avoid mixing multithreading and fork()fork() 紧跟在 exec() 后面就可以了)。 @J.F.Sebastian:是的,回复:经验法则以及混合线程和分叉的危险。但是,我会质疑将execve() 定性为“阻塞”。成功的execve 不会阻止调用者,它会蒸发调用者。【参考方案2】:

由于subprocess 用于运行可执行文件(它本质上是os.fork()os.execve() 的包装),使用它可能更有意义。您可以使用subprocess.Popen。比如:

 import subprocess

 process = subprocess.Popen(["binary"])

这将作为一个单独的进程运行,因此不受 GIL 的影响。然后您可以使用Popen.poll() 方法检查子进程是否已终止:

if process.poll():
    # process has finished its work
    returncode = process.returncode

只需确保您没有调用任何等待进程完成其工作的方法(例如Popen.communicate()),以避免您的 Python 脚本阻塞。

如this answer中所述

multiprocessing 用于在现有的函数中运行 (Python) 代码,支持更灵活的通信 进程族。 multiprocessing 模块旨在提供 与线程非常相似的接口和功能,而 允许 CPython 在多个 CPU/内核之间扩展您的处理 尽管有 GIL。

因此,鉴于您的用例,subprocess 似乎是正确的选择。

【讨论】:

process.stdout.readlines() 如果任何子进程填满了它们的任何 stderr 管道缓冲区,则可能会永远阻塞。如果你想分别读取标准输出和标准错误,那么你需要asynchronous approach: threads or non-blocking pipes or iocp on Windows 完全正确!我已经忘记了。谢谢。【参考方案3】:

GIL 不跨越多个进程。 subprocess.Popen 开始一个新进程。如果它启动一个 Python 进程,那么它将拥有自己的 GIL。

如果您只想并行运行一些 linux 二进制文件,则不需要多个线程(或由 multiprocessing 创建的进程):

from subprocess import Popen

# start all processes
processes = [Popen(['program', str(i)]) for i in range(10)]
# now all processes run in parallel

# wait for processes to complete
for p in processes:
    p.wait()

你可以use multiprocessing.ThreadPool to limit number of concurrently run programs。

【讨论】:

应该是multiprocessing.Pool @DanqiWang:没有。 multiprocessing 提供具有相同接口的基于进程的 Pool 和基于线程的 Pool。两者都可以根据情况使用。 我不明白你为什么偏爱TheadPool 而不是poolPool 还可以限制并发数,运行的进程不会受到 GIL 的影响。此外,ThreadPool 的文档记录也很差。 @DanqiWang:Popen启动new进程;正如答案中的第一段所说,没有 GIL 问题。您可以使用from multiprocessing.dummy import Pool(与ThreadPool 相同)然后将代码从使用线程更改为使用进程只需从导入中删除.dummy。界面相同。 明白。没注意到是Popen。我的错。感谢您的解释。

以上是关于使用 subprocess 模块是不是会释放 python GIL?的主要内容,如果未能解决你的问题,请参考以下文章

Python之subprocess模块的使用

使用 subprocess 模块访问无密码 SSH 设备

Python__subprocess模块

Python subprocess shell 编程规范

常用模块(subprocess/hashlib/configparser/logging/re)

python使用subprocess及delegator调用第三方程序