Python 子进程挂起命名管道

Posted

技术标签:

【中文标题】Python 子进程挂起命名管道【英文标题】:Python subprocess hangs with named pipes 【发布时间】:2013-11-08 12:37:06 【问题描述】:

苦于试图模仿bash这个简单的片段:

$ cat /tmp/fifo.tub &                                                            
[1] 24027
$ gunzip -c /tmp/filedat.dat.gz > /tmp/fifo.tub                                                              
line 01
line 02
line 03
line 04
line 05
line 06
line 07
line 08
line 09
line 10
[1]+  Done                    cat /tmp/fifo.tub

基本上我尝试了这种subprocess 方法:

# -*- coding: utf-8 -*-

import os
import sys
import shlex
import pprint
import subprocess

def main():

    fifo = '/tmp/fifo.tub'
    filedat = '/tmp/filedat.dat.gz '
    os.mkfifo(fifo,0777)
    cat  = "cat %s" % fifo
    args_cat = shlex.split(cat)
    pprint.pprint(args_cat)

    cat = subprocess.Popen( args_cat,
                            close_fds=True,
                            preexec_fn=os.setsid)

    print "PID cat: %s" % cat.pid

    f = os.open(fifo ,os.O_WRONLY)

    gunzip = 'gunzip -c  %s' %  (filedat)
    args_gunzip = shlex.split(gunzip)
    pprint.pprint(args_gunzip)

    gunzip = subprocess.Popen( args_gunzip,
                               stdout = f,
                               close_fds=True,
                               preexec_fn=os.setsid)

    print "PID gunzip: %s" % gunzip.pid

    while not cat.poll():
        # hangs for ever
        pass
    return True

if __name__=="__main__":
    main()

cat 进程永远结束。

另外,我尝试用 threads 绕过问题,但得到了相同的结果。

import os
import sys
import shlex
import pprint
import subprocess
import threading

class Th(threading.Thread):
    def __init__(self,cmd,stdout_h=None):
        self.stdout = None
        self.stderr = None
        self.cmd = cmd
        self.stdout_h = stdout_h

        self.proceso = None
        self.pid = None

        threading.Thread.__init__(self)

    def run(self):
        if self.stdout_h:
            self.proceso = subprocess.Popen(self.cmd,
                                 shell=False,
                                 close_fds=True,
                                 stdout=self.stdout_h)

        else:
            self.proceso = subprocess.Popen( self.cmd,
                                  close_fds=True,
                                  shell=False)
        print "PID: %d" % self.proceso.pid   


def main():

    fifo = '/tmp/fifo.tub'
    filedat = '/tmp/filedat.dat.gz '

    try:
        os.unlink(fifo)
    except:
        pass
    try:
       os.mkfifo(fifo,0777)
    except Exception , err:
       print "Error '%s' tub %s." % (err,fifo)
       sys.exit(5)

    cat  = "cat %s" % fifo
    args_cat = shlex.split(cat)
    pprint.pprint(args_cat)

    cat = Th(cmd=args_cat)
    cat.start()

    try:
        f = os.open(fifo ,os.O_WRONLY)
    except Exception, err:
        print  "Error '%s' when open fifo %s " % (err,fifo)
        sys.exit(5)

    gunzip = 'gunzip -c  %s ' %  (filedat)
    args_gunzip = shlex.split(gunzip)
    pprint.pprint(args_gunzip)

    gunzip = Th(cmd=args_gunzip,stdout_h=f)
    gunzip.start()
    gunzip.join()
    cat.join()

    while  gunzip.proceso.poll() is None:
        pass

    if  cat.proceso.poll() is None:
        print "Why?"
        cat.proceso.terminate() 
    return True

if __name__=="__main__":
    main()

我显然遗漏了一些东西,非常欢迎任何帮助。

【问题讨论】:

【参考方案1】:

您没有关闭 FIFO 文件描述符,所以 cat 只是挂在那里,以为还有更多内容。

我认为您也可以使用 .wait() 方法来执行与您的 while 循环相同的操作。

# -*- coding: utf-8 -*-

import os
import sys
import shlex
import pprint
import subprocess

def main():

    fifo = '/tmp/fifo.tub'
    filedat = '/tmp/filedat.dat.gz '
    os.mkfifo(fifo,0777)
    cat  = "cat %s" % fifo
    args_cat = shlex.split(cat)
    pprint.pprint(args_cat)

    cat = subprocess.Popen( args_cat,
                            close_fds=True,
                            preexec_fn=os.setsid)

    print "PID cat: %s" % cat.pid

    f = os.open(fifo ,os.O_WRONLY)

    gunzip = 'gunzip -c  %s' %  (filedat)
    args_gunzip = shlex.split(gunzip)
    pprint.pprint(args_gunzip)

    gunzip = subprocess.Popen( args_gunzip,
                               stdout = f,
                               close_fds=True,
                               preexec_fn=os.setsid)

    print "PID gunzip: %s" % gunzip.pid

    gunzip.wait()
    print "gunzip finished"
    os.close(f)
    cat.wait()
    print "cat finished"

    return True

if __name__=="__main__":
    main()

【讨论】:

这对你有用吗?我唯一担心的是它在 cat 完成之前关闭文件描述符,但我怀疑它只是为 python 进程关闭它而不是 cat 即使在gunzipexeced 之前发生重定向,您也可以关闭父管道的写入端。 with-statement can help with cleaning things up 成功了@Necrolyte2! ,来自 bash 的隐式关闭很容易跳过 我也花了一段时间才找到错误。确保查看@J.F.Sebastian 发布的 GIST 以及一些很棒的语句用法。 @klashxx:在这种情况下,您需要在gunzip 调用之后添加cat.wait()(在我的代码中,它由with-statement 自动调用)

以上是关于Python 子进程挂起命名管道的主要内容,如果未能解决你的问题,请参考以下文章

尝试将 StreamWriter 打开到命名管道时,Mono 挂起

使用命名管道向子进程发送参数

管道和命名管道

Linux:打开命名管道进行写入时超时

将子进程的 stdout 和 stderr 重定向到两个命名管道(然后从它们读回)

C命名管道不适用于多进程