为什么我的多线程python脚本使用Queue,threading.Thread和subprocess如此不稳定

时间:2010-12-28 22:02:35

标签: python multithreading subprocess

我有三个shell脚本P1,P2和P3,我试图链接。这三个shell脚本需要串行运行,但在任何给定时间都可以运行多个P1,P2和P3。

我需要在数十个文件上快速运行这些文件,因此需要使用Threads并且并行执行。

我正在使用python Thread,Queue和subprocess模块​​来实现这一目标。

我的问题是,当我的线程数大于1时,程序行为不规律,并且线程不会以可重现的方式相互交换。所有五个线程都可以完美运行并完成工作。

这是我第一次尝试使用线程进行操作,我确信这是因为Threads涉及竞争条件的常见问题。但我想知道如何清理我的代码。

实际代码位于(https://github.com/harijay/xtaltools/blob/master/process_multi.py)。伪代码如下。对不起,如果代码很乱。

我的问题是为什么我使用这种设计会有不稳定的行为。线程在任何给定时间都访问不同的文件。此外,subprocess.call仅在shell脚本完成且其生成的文件写入磁盘时返回。

我能做些什么不同的事情? 我试图尽可能简洁地解释我的设计。

我的基本设计:

P1_Queue = Queue()
P2_Queue = Queue()
P3_Queue = Queue()

class P1_Thread(Thread):
    def __init__(self,P1_Queue,P2_Queue):
        Thread.__init__(self)
        self.in_queue = P1_Queue
        self.out_queue = P2_Queue

    def run(self):
        while True:
            my_file_to_process = self.in_queue.get()
            if my_file_to_process = None:
                break
            P1_runner = P1_Runner(my_file_to_process)
            P1_runner.run_p1_using_subprocess()
            self.out_queue.put(my_file_to_process)

类p1 Runner获取输入文件句柄,然后调用subprocess.call()以运行使用文件输入的shell脚本,并使用run_p1_using_subprocess方法生成新的输出文件。

class P1_runner(object):

    def __init__(self,inputfile):
        self.my_shell_script = """#!/usr/bin/sh
prog_name <<eof
input 1
...
eof"""
       self.my_shell_script_file = open("some_unique_p1_file_name.sh")
       os.chmod("some_unique_file_name.sh",0755)

    def run_p1_using_subprocess(self):
        subprocess.call([self.my_shell_script_file])

I have essentially similar classes for P2 and P3 . All of which call a shell script that is custom generated

The chaining is achieved using a series of Thread Pools.
p1_worker_list = []
p2_worker_list = []
p3_worker_list = []

for i in range(THREAD_COUNT):
    p1_worker = P1_Thread(P1_Queue,P2_Queue)
    p1_worker.start()
    p1_worker_list.append(p1_worker)

for worker in p1_worker_list:
    worker.join()

And then again the same code block for p2 and p3

for i in range(THREAD_COUNT):
    p2_worker = P2_Thread(P2_Queue,P3_Queue)
    p2_worker.start()
    p2_worker_list.append(p1_worker)

for worker in p2_worker_list:
    worker.join()

感谢您的帮助/建议

2 个答案:

答案 0 :(得分:2)

这真的很糟糕:

runner.run()

你不应该手动调用线程的run方法。你用.start()开始一个线程。你的代码是一个巨大的混乱,这里没有人会通过它来找到你的错误。

答案 1 :(得分:1)

当另一个线程清空其输入队列时,线程的退出条件会使它们自杀:

    my_file_to_process = self.in_queue.get()
    if my_file_to_process = None:  # my sister ate faster than I did, so...
        break # ... I kill myself!

线程正在消亡只是因为当他们准备好更多时他们没有找到工作要做。

你应该让线程进入休眠状态(等待)直到它们的输入队列上的事件发出信号,只有当协调器(主程序)发出信号表明处理完成时才设置死亡(设置自杀标志,并发出所有队列信号) )。

(我看到你已经改变了代码)。

@Falmarri在其他地方的说明中可能意味着你的问题不是关于特定问题(其他人可以回答的问题),因为代码中threading库的整体使用是错误的,并且你使用的是编程语言一般都很尴尬。例如:

  • worker.join()的调用使主程序在启动P2线程之前按顺序等待所有P1线程的终止,从而击败任何并发尝试。
  • 您应该覆盖Thread.run()或为构造函数提供可调用项。不需要Pn_runner类。
  • 所有线程类都是一样的。哟每个流程阶段不需要不同的课程。
  • 如果您已经在使用Python,那么调用外部程序(更不用说shell脚本)是没有意义的,除非您绝对不能在纯Python中轻松完成工作。
  • 由于上述原因,让您的程序将shell脚本写入文件系统是非常奇怪的,几乎肯定是不必要的。

我建议你解决这个特殊问题的建议是:

  1. 尽量坚持使用100%的Python。如果您不能,或者看起来太难,您至少会找到必须从外部访问的特定功能。
  2. 构建不使用并发的解决方案。
  3. 测量程序的性能并尝试通过算法进行改进。
  4. 如果可以,请避免穿线。受CPU限制的程序将占用所有可用的循环而不进行线程化。如果没有其他任何操作,那么太磁盘绑定(或绑定任何外部/远程资源)的程序将最终等待磁盘。要从线程中受益,程序必须在计算和外部资源使用之间保持适当的平衡(或者即使在忙碌时也必须能够在到达时为其提供服务)。
  5. pythonic 的方式做到:开始简单,逐步增加功能和复杂性,同时避免任何看似复杂的事情。
  6. 如果您打算自学Python中的线程,那么请务必寻找一个简单的问题来进行实验。如果您想要的是并行运行多个shell脚本,那么bash和其他shell已经有了相应的规定,您不需要使用Python。