Python 子进程在管道输出时作为 Popen 挂起
Python subprocess hangs as Popen when piping output
我已经阅读了这里的数十篇 "Python subprocess hangs" 文章,并且认为我已经解决了以下代码中各种文章中提出的所有问题。
我的代码间歇性地挂在 Popen 命令处。我正在 运行 使用 multiprocessing.dummy.apply_async 宁 4 个线程,每个线程启动一个子进程,然后逐行读取输出并将其修改版本打印到标准输出。
def my_subproc():
exec_command = ['stdbuf', '-i0', '-o0', '-e0',
sys.executable, '-u',
os.path.dirname(os.path.realpath(__file__)) + '/myscript.py']
proc = subprocess.Popen(exec_command, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1)
print "DEBUG1", device
for line in iter(proc.stdout.readline, b''):
with print_lock:
for l in textwrap.wrap(line.rstrip(), LINE_WRAP_DEFAULT):
上面的代码是 运行 来自 apply_async:
pool = multiprocessing.dummy.Pool(4)
for i in range(0,4):
pool.apply_async(my_subproc)
子进程间歇性地挂在 subprocess.Popen
,语句 "DEBUG1" 不打印。有时所有线程都会工作,有时只有 4 个线程中的 1 个会工作。
我不知道这显示了 Popen 的任何已知死锁情况。我错了吗?
这似乎是与 multiprocessing.dummy 的不良互动。当我使用多处理(不是 .dummy 线程接口)时,我无法重现错误。
subprocess.Popen() 中存在一个由 stdout(可能是 stderr)的 io 缓冲引起的隐患。 child 进程 io 缓冲区中有大约 65536 个字符的限制。如果 child 进程写入足够的输出,child 进程将 "hang" 等待缓冲区被刷新 - 死锁情况。 subprocess.py 的作者似乎认为这是由 child 引起的问题,尽管 subprocess.flush 会受到欢迎。皮尔逊安德斯皮尔逊,
https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/ 有一个简单的解决方案,但你必须注意。正如他所说,"tempfile.TemporaryFile() is your friend." 在我的例子中,我是 运行 一个循环中的应用程序来批处理一堆文件,解决方案的代码是:
with tempfile.TemporaryFile() as fout:
sp.run(['gmat', '-m', '-ns', '-x', '-r', str(gmat_args)], \
timeout=cpto, check=True, stdout=fout, stderr=fout)
上面的修复在处理了大约 20 个文件后仍然死锁。一个改进但还不够好,因为我需要批量处理数百个文件。我想出了下面的 "crowbar" 方法。
proc = sp.Popen(['gmat', '-m', '-ns', '-x', '-r', str(gmat_args)], stdout=sp.PIPE, stderr=sp.STDOUT)
""" Run GMAT for each file in batch.
Arguments:
-m: Start GMAT with a minimized interface.
-ns: Start GMAT without the splash screen showing.
-x: Exit GMAT after running the specified script.
-r: Automatically run the specified script after loading.
Note: The buffer passed to Popen() defaults to io.DEFAULT_BUFFER_SIZE, usually 62526 bytes.
If this is exceeded, the child process hangs with write pending for the buffer to be read.
https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/
"""
try:
(outs, errors) = proc.communicate(cpto)
"""Timeout in cpto seconds if process does not complete."""
except sp.TimeoutExpired as e:
logging.error('GMAT timed out in child process. Time allowed was %s secs, continuing', str(cpto))
logging.info("Process %s being terminated.", str(proc.pid))
proc.kill()
""" The child process is not killed by the system. """
(outs, errors) = proc.communicate()
""" And the stdout buffer must be flushed. """
基本思路是在每次超时时终止进程并刷新缓冲区。我将 TimeoutExpired 异常移到批处理循环中,以便在终止该进程后继续下一个进程。如果超时值足以让 gmat 完成(尽管速度较慢),这是无害的。我发现代码会在超时前处理 3 到 20 个文件。
这看起来确实像是子进程中的错误。
我已经阅读了这里的数十篇 "Python subprocess hangs" 文章,并且认为我已经解决了以下代码中各种文章中提出的所有问题。
我的代码间歇性地挂在 Popen 命令处。我正在 运行 使用 multiprocessing.dummy.apply_async 宁 4 个线程,每个线程启动一个子进程,然后逐行读取输出并将其修改版本打印到标准输出。
def my_subproc():
exec_command = ['stdbuf', '-i0', '-o0', '-e0',
sys.executable, '-u',
os.path.dirname(os.path.realpath(__file__)) + '/myscript.py']
proc = subprocess.Popen(exec_command, env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1)
print "DEBUG1", device
for line in iter(proc.stdout.readline, b''):
with print_lock:
for l in textwrap.wrap(line.rstrip(), LINE_WRAP_DEFAULT):
上面的代码是 运行 来自 apply_async:
pool = multiprocessing.dummy.Pool(4)
for i in range(0,4):
pool.apply_async(my_subproc)
子进程间歇性地挂在 subprocess.Popen
,语句 "DEBUG1" 不打印。有时所有线程都会工作,有时只有 4 个线程中的 1 个会工作。
我不知道这显示了 Popen 的任何已知死锁情况。我错了吗?
这似乎是与 multiprocessing.dummy 的不良互动。当我使用多处理(不是 .dummy 线程接口)时,我无法重现错误。
subprocess.Popen() 中存在一个由 stdout(可能是 stderr)的 io 缓冲引起的隐患。 child 进程 io 缓冲区中有大约 65536 个字符的限制。如果 child 进程写入足够的输出,child 进程将 "hang" 等待缓冲区被刷新 - 死锁情况。 subprocess.py 的作者似乎认为这是由 child 引起的问题,尽管 subprocess.flush 会受到欢迎。皮尔逊安德斯皮尔逊, https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/ 有一个简单的解决方案,但你必须注意。正如他所说,"tempfile.TemporaryFile() is your friend." 在我的例子中,我是 运行 一个循环中的应用程序来批处理一堆文件,解决方案的代码是:
with tempfile.TemporaryFile() as fout:
sp.run(['gmat', '-m', '-ns', '-x', '-r', str(gmat_args)], \
timeout=cpto, check=True, stdout=fout, stderr=fout)
上面的修复在处理了大约 20 个文件后仍然死锁。一个改进但还不够好,因为我需要批量处理数百个文件。我想出了下面的 "crowbar" 方法。
proc = sp.Popen(['gmat', '-m', '-ns', '-x', '-r', str(gmat_args)], stdout=sp.PIPE, stderr=sp.STDOUT)
""" Run GMAT for each file in batch.
Arguments:
-m: Start GMAT with a minimized interface.
-ns: Start GMAT without the splash screen showing.
-x: Exit GMAT after running the specified script.
-r: Automatically run the specified script after loading.
Note: The buffer passed to Popen() defaults to io.DEFAULT_BUFFER_SIZE, usually 62526 bytes.
If this is exceeded, the child process hangs with write pending for the buffer to be read.
https://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/
"""
try:
(outs, errors) = proc.communicate(cpto)
"""Timeout in cpto seconds if process does not complete."""
except sp.TimeoutExpired as e:
logging.error('GMAT timed out in child process. Time allowed was %s secs, continuing', str(cpto))
logging.info("Process %s being terminated.", str(proc.pid))
proc.kill()
""" The child process is not killed by the system. """
(outs, errors) = proc.communicate()
""" And the stdout buffer must be flushed. """
基本思路是在每次超时时终止进程并刷新缓冲区。我将 TimeoutExpired 异常移到批处理循环中,以便在终止该进程后继续下一个进程。如果超时值足以让 gmat 完成(尽管速度较慢),这是无害的。我发现代码会在超时前处理 3 到 20 个文件。
这看起来确实像是子进程中的错误。