子进程不是线程安全的,替代方案?

时间:2017-09-05 11:52:52

标签: python multithreading python-2.7

我正在使用python 2.7并且无法升级或反向移植subprocess32。我在线程环境中使用它,通常它工作正常,但有时subprocess创建没有返回,因此线程挂起,即使strace在挂起的实例中也不起作用,所以我没有得到反馈。

E.G。此行可能导致挂起(返回的数据很小,因此不是管道问题):

process = subprocess.Popen(cmd,
          stdout=subprocess.PIPE,
          stderr=subprocess.STDOUT)

我后来读到python 2.7中的子进程不是线程安全的,并且在最新版本中修复了“各种问题”。我正在使用多个线程调用子进程。

我用以下代码(作为一个可证明的示例 - 而不是我的实际代码)演示了这个问题,该代码以每个subprocess启动多个线程:

import os, time, threading, sys
from subprocess import Popen
i=0
class Process:
    def __init__(self, args):
        self.args = args

    def run(self):
        global i
        retcode = -1
        try:
                self.process = Popen(self.args)
                i+=1
                if i == 10:
                    sys.stdout.write("Complete\n")
                while self.process.poll() is None:
                    time.sleep(1.0)
                retcode = self.process.returncode
        except:
            sys.stdout.write("ERROR\n")

        return retcode

def main():
    processes = [Process(["/bin/cat"]) for _ in range(10)]
    # start all processes
    for p in processes:
        t = threading.Thread(target=Process.run, args=(p,))
        t.daemon = True
        t.start()
    sys.stdout.write("all threads started\n")
    # wait for Ctrl+C
    while True:
        time.sleep(1.0)

main()

这通常会导致一个或多个subprocess来电永不返回。有没有人有关于此或解决方案/替代方案的更多信息

我正在考虑使用弃用的commands.getoutput,但不知道这是否是线程安全的?它似乎对上面的代码正常工作。

1 个答案:

答案 0 :(得分:2)

如果您的线程所做的大部分工作只是在等待子进程,那么您可以使用协同程序更有效地实现这一点。使用python2,您可以使用生成器来实现这一点,因此run函数的必要更改是:

  1. time.sleep(1.0)替换为yield,将控件传递给其他例程
  2. return retcode替换为self.retcode = retcode或类似内容,因为生成器无法返回值before python3.3
  3. 然后main函数可能是这样的:

    def main():
        processes = [Process(["/bin/cat"]) for _ in range(10)]
        #since p.run() is a generator this doesn't run any of the code yet
        routines = [p.run() for p in processes]
        while routines:
            #iterate in reverse so we can remove routines while iterating without skipping any
            for routine in reversed(routines): 
                try:
                    next(routine) #continue the routine to next yield
                except StopIteration:
                    #this routine has finished, we no longer need to check it
                    routines.remove(routine)
    

    这是为了给你一个开始的地方,我建议在收益率周围添加print语句或使用pythontutor来更好地理解执行的顺序。

    这样做的好处是永远不会让任何线程等待任何事情,只需要一个线程一次执行一段处理,这比许多空闲线程更有效。