通过Python的subprocess
模块调用需要相对较长时间的Linux二进制文件,是否会释放GIL?
我想并行化一些调用命令行二进制程序的代码。使用线程(通过threading
和multiprocessing.pool.ThreadPool
)还是multiprocessing
更好?我的假设是如果subprocess
释放了GIL,那么选择threading
选项更好。
通过Python的subprocess
模块调用需要相对较长时间的Linux二进制文件,是否会释放GIL?
我想并行化一些调用命令行二进制程序的代码。使用线程(通过threading
和multiprocessing.pool.ThreadPool
)还是multiprocessing
更好?我的假设是如果subprocess
释放了GIL,那么选择threading
选项更好。
subprocess
模块调用一个运行时间相对较长的Linux二进制文件时,这是否会释放GIL?subprocess
提供了方便的接口,可以在fork
,execve
和waitpid
的基础上进行操作。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来进行测试。
waitpid()
,CPython会释放全局解释器锁(GIL)。 subprocess
模块的方法没有什么特别之处。注意:execve()
显然会阻塞(在这种情况下是在子进程中)——它仅在出错时返回。 fork()
是一个特例:阅读此讨论以了解为什么应避免混合多线程和fork()
(fork()
立即接着执行 exec()
是可以的)。 - jfsexecve()
被描述为“阻塞”的说法提出质疑。成功执行execve()
并不会阻塞调用者,而是使其消失。 - pilcrowGIL无法跨越多个进程。 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
来限制同时运行的程序数量。multiprocessing
提供了基于进程和基于线程的池,两者接口相同。根据情况可以选择使用其中之一。 - jfsfrom multiprocessing.dummy import Pool
(与ThreadPool
相同),然后您只需要从导入中删除.dummy
,就可以将代码从使用线程更改为使用进程。接口是相同的。 - jfsPopen
。我的错。谢谢你的解释。 - Danqi Wangsubprocess
用于运行可执行文件(实际上它是os.fork()
和os.execve()
的包装器),因此使用它可能更有意义。您可以使用subprocess.Popen
。类似这样的代码: import subprocess
process = subprocess.Popen(["binary"])
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()
可能会永远阻塞。 如果您想分别读取stdout和stderr,则需要异步方法:线程或非阻塞管道或Windows上的iocp。 - jfs
subprocess.call()
或subprocess.Popen(...).wait()
会阻塞调用者中的其他线程。(它们不会阻塞。) - pilcrow