Python中的可重入子进程?
我有一个Python模块,它使用subprocess
模块来处理一些任务。这个模块通过subprocess.communicate
方法来读取和写入数据,使用的是stdin
和stdout
这两个管道。其中一个子进程又重新进入了这个Python模块,并且启动了另一个子进程。这导致了应用程序的死锁,因为stdin
和stdout
的文件描述符已经被父进程占用了。
有没有办法避免这种死锁,而不需要到处创建和清理临时文件呢?
这里是我场景的详细信息:这是一个在FastCGI服务器上运行的网页应用。当请求生成一个PDF文件时,会启动一个子进程来调用一个第三方应用(wkhtmltopdf)来创建PDF。这个应用接着开始通过我的FastCGI模块下载图片——这发生在与PDF创建者的父进程同一个进程中。获取图片时又会调用另一个第三方应用,通过subprocess
来执行,这就导致了死锁,因为stdin
和stdout
已经被PDF创建者的子进程占用了。
这个问题在这篇博客文章(最后部分)中提到过,但没有提供后续的解决方案。我可能不得不使用临时文件,但我更喜欢使用管道。有没有人遇到过这个问题?
1 个回答
我觉得你的分析不太对。
你说进程出现死锁是因为PDF创建进程正在使用标准输入和标准输出。但是,由于这个进程是通过子进程模块启动的,所以PDF创建进程的标准输入和标准输出其实只是FastCGI进程的普通管道文件描述符。没有理由不能同时有多个子进程的“通信”调用在进行。
不过要注意,“通信”是一个阻塞调用。当你的进程中的一个线程在执行通信时,这个线程就不能做其他事情,比如处理图片的HTTP请求。
在这种情况下,一个解决方案是让你的服务器支持多线程。我有点惊讶它还不是多线程的,因为大多数网络服务器都能同时处理多个请求(在不同的线程中),所以这应该是“可以正常工作的”。
但也许你在使用“通信”的方式上有些问题。我写了一个小例子,展示如何在同一个进程中同时进行多个“通信”调用,也许你可以用它作为解决方案的基础。如果不了解你的具体问题,很难提供更好的帮助。
import subprocess
from threading import Thread
sp1 = subprocess.Popen(["bash","-c","sleep 2;echo output1"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,shell=False,close_fds=True)
sp2 = subprocess.Popen(["bash","-c","sleep 1;echo output2"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,shell=False,close_fds=True)
def readfrom(which,sp):
print "Thread #%d starting."%(which,)
(stdout, stderr) = sp.communicate()
print "Thread #%d finished, output: %s"%(which,stdout)
t1=Thread(target=readfrom,args=(1,sp1))
t2=Thread(target=readfrom,args=(2,sp2))
t1.start()
t2.start()
t1.join()
t2.join()