使用 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?
我想并行化一些从命令行调用二进制程序的代码。使用线程(通过threading
和multiprocessing.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 平台上,subprocess
在fork
、execve
和waitpid
的“原始”组件之上提供了便利的接口。
通过检查 CPython 2.7.9 源代码,fork
和 execve
不发布 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
而不是pool
。 Pool
还可以限制并发数,运行的进程不会受到 GIL 的影响。此外,ThreadPool
的文档记录也很差。
@DanqiWang:Popen启动new进程;正如答案中的第一段所说,没有 GIL 问题。您可以使用from multiprocessing.dummy import Pool
(与ThreadPool
相同)然后将代码从使用线程更改为使用进程只需从导入中删除.dummy
。界面相同。
明白。没注意到是Popen
。我的错。感谢您的解释。以上是关于使用 subprocess 模块是不是会释放 python GIL?的主要内容,如果未能解决你的问题,请参考以下文章