如何在Python中管理大量输出的子进程?
我正在用一个bash脚本控制一些长时间运行的模拟,这些模拟可能持续几个小时、几天,甚至几周。这个脚本会遍历所有需要的参数。如果只有一个模拟在运行,输出会通过“tee”命令处理;如果有多个模拟同时运行,输出就直接写入一个输出文件。所有的输出文件都非常大:有些日志文件大约有2GB,甚至可能更大。
这个脚本虽然能正常工作,但维护起来非常麻烦。每次添加一个新参数时,都需要花时间去调整脚本和里面的各种命令。所以我把它移植到了Python,效果非常好。
现在唯一让我无法在生产环境中使用它的问题是,我找不到正确的方法来调用Popen()来启动程序。如果我选择“静默”运行,把所有输出都写入文件而不显示任何内容,Python在模拟完成之前会占用几GB的内存。
以下是代码片段:
fh = open(logfile, "w")
pid = subprocess.Popen(shlex.split(command), stdout=fh)
pids.append(pid)
我读了很多关于Popen输出的资料,但我以为把输出写入文件时,缓冲区会在需要时自动刷新吧?
也许subprocess的Popen()并不是最好的选择?有没有更好的方法可以同时在屏幕上和文件中显示和保存程序的输出,而不会占用太多内存?
谢谢!
4 个回答
如果输出中有可靠的分隔符(也就是标记输出部分结束的符号),可以考虑做一些“坏事”,也就是在一个单独的线程中读取子进程的标准输出,并把每一小块内容写入日志,每次写入时都刷新一下。
你可以看看这里的一些例子,了解如何从子进程的管道中进行非阻塞读取:
为什么不静默地写入一个文件,然后再去查看它的最后几行呢?
你可以使用 file.flush()
来清空Python的文件缓存。
Python可以很方便地处理当前打开文件中的新行。例如:
f = open( "spam.txt", "r" )
f.read()
# 'I like ham!'
# Now open up spam.txt in some other program and add a new line.
f.read()
# 'I like eggs too!'
最简单的解决办法就是把代码改成同时输出到标准输出和一个日志文件。这样,输出就不需要用tee或者管道来保存了。
pipe_verbose = sys.stdout
pipe_silent = open('/dev/null', 'w')
subprocess.Popen(shlex.split(command), stdout=pipe_silent)
subprocess.Popen(shlex.split(command), stdout=pipe_verbose)
最后,我用poll()来检查什么时候完成。
使用管道的好处是,如果我按下ctrl+c来终止脚本,它也会把相关的工作一起杀掉。如果我在Popen()里没有设置stdout=...,那么这个工作就会在后台继续运行。而且,这样的话,Python的CPU使用率会保持在0%。如果在管道上使用readline循环,CPU使用率就会飙升到100%...