关于多线程:使用子进程模块是否释放python GIL?

Does using the subprocess module release the python GIL?

当通过python的subprocess模块调用一个Linux二进制文件需要相当长的时间时,这会释放gil吗?

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


When calling a linux binary which takes a relatively long time through Python's subprocess module, does this release the GIL?

是的,它在调用过程中释放全局解释器锁(gil)。

正如您可能知道的,在POSIX平台上,subprocessforkexecvewaitpid的"原始"组件上提供了方便的接口。

通过对cpython 2.7.9来源的检查,forkexecve不释放gil。但是,这些电话不会阻塞,所以我们不希望GIL被释放。

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

1
2
3
4
5
6
7
8
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创建的进程):

1
2
3
4
5
6
7
8
9
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。比如:

1
2
3
 import subprocess

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

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

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

只需要确保不调用等待进程完成其工作的任何方法(例如popen.communication()),以避免您的python脚本阻塞。

如本答案所述

multiprocessing is for running functions within your existing
(Python) code with support for more flexible communications among the
family of processes. multiprocessing module is intended to provide
interfaces and features which are very similar to threading while
allowing CPython to scale your processing among multiple CPUs/cores
despite the GIL.

因此,考虑到您的用例,subprocess似乎是正确的选择。