最新消息:

小心subprocess的PIPE卡住你的python程序

python admin 17114浏览 0评论

python 2.4后引入新的模块subprocess,用于取代原有的commands模块。利用subprocess可以非常方便地跑多个后台任务,例如下面的示例代码(省去所有的错误处理):

from subprocess import Popen, PIPE
import time
procs = []
for cmd in cmd_list:
    p = Popen(cmd,stdout=PIPE,stderr=PIPE, shell=True)
    procs.append(p)
all_procs = len(procs)
done_procs = 0
while done_procs < all_procs:
    time.sleep(0.1)
    for p in procs:
        if p.poll():
            done_procs += 1
            #do something done this process
        else:
            continue

有了subprocess之后,生活变得更加美好了,但是,上面的代码一不小心就会让你掉入难以追查的坑中,subprocess调用的子进程可能hang住,而poll会永远无法等到子进程结束。Complete deadlock!

经过多次试错后发现,cmd命令输出(标准或者错误)小于65536字节时,程序运行完全正常,当输出大于等于65536后,程序立马hang住,看来问题就可能出在输出过多数据上。

查看python的源码发现,如果设置了stdout或stderr,subprocess就会调用os.pipe创建一个管道用于其和子进程之间的通信,而上面的问题正好是cmd输出的数据把pipe塞满,无法继续往pipe里写入数据导致程序hang住,而我们没有去读出pipe数据,而是死等子进程完成,导致死锁。

解决上面的问题可以有两种简单的方法:

1、使用文件代替PIPE,解除PIPE大小的限制:

from subprocess import Popen, PIPE
import time
procs = []
for cmd in cmd_list:
    fdout = open(len(procs)+".out", 'w')
    fderr = open(len(procs)+".err", 'w')
    p = Popen(cmd,stdout=fdout,stderr=fderr, shell=True)
    procs.append([p,fdout,fderr])
all_procs = len(procs)
done_procs = 0
while done_procs < all_procs:
    time.sleep(0.1)
    for p in procs:
        if p[0].poll():
            done_procs += 1
            #do something done this process
        else:
            continue

2、使用communicate及时读出pipe中内容,避免堵死,但在输出量非常大的情况下会影响性能:

from subprocess import Popen, PIPE
import time
procs = []
for cmd in cmd_list:
    p = Popen(cmd,stdout=PIPE,stderr=PIPE, shell=True)
    procs.append([p, '', ''])
all_procs = len(procs)
done_procs = 0
while done_procs < all_procs:
    time.sleep(0.1)
    for p in procs:
        if p[0].poll():
            done_procs += 1
            #do something done this process
        else:
            out, err = p[0].communicate()
            p[1] += out
            p[2] += err

转载请注明:爱开源 » 小心subprocess的PIPE卡住你的python程序

您必须 登录 才能发表评论!