当通过Python的
subprocess
模块调用需要相对较长时间的linux二进制文件时,这会释放GIL吗?
我想并行化一些从命令行调用二进制程序的代码。使用线程(通过
threading
和 multiprocessing.pool.ThreadPool
)还是 multiprocessing
更好?我的假设是,如果 subprocess
释放 GIL,那么选择 threading
选项会更好。
当通过Python的
模块调用需要相对较长时间的linux二进制文件时,这会释放GIL吗?subprocess
是的,它在调用过程中释放了全局解释器锁(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)来进行测试。
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()
您可以使用
multiprocessing.ThreadPool
来限制同时运行的程序数量。
由于
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
只需确保您不调用任何 wait 等待进程完成其工作的方法(例如 Popen.communicate())以避免 Python 脚本阻塞。
正如这个答案
中提到的用于在现有的 (Python) 代码支持更灵活的通信 进程族。multiprocessing
模块旨在提供 接口和功能与线程非常相似,而 允许 CPython 在多个 CPU/核心之间扩展处理 尽管有 GIL。multiprocessing
因此,考虑到您的用例,
subprocess
似乎是正确的选择。