为什么我使用Queue、threading.Thread和subprocess的多线程python脚本如此脆弱

为什么我使用Queue、threading.Thread和subprocess的多线程python脚本如此脆弱,python,multithreading,subprocess,Python,Multithreading,Subprocess,我有三个shell脚本P1、P2和P3,我正在尝试链接它们。这三个shell脚本需要串联运行,但在任何给定时间都可以运行多个p1、p2和p3 我需要在数十个文件上快速运行这些程序,因此需要使用线程并并行地完成任务 我正在使用python线程、队列和子流程模块来实现这一点 我的问题是,当我的线程数大于1时,程序的行为不稳定,线程不会以可复制的方式相互切换。有时,所有五个线程都能完美地工作并完成 这是我第一次尝试使用线程来做一些事情,我确信这是因为线程通常会遇到一些涉及争用条件的问题。但我想知道我该

我有三个shell脚本P1、P2和P3,我正在尝试链接它们。这三个shell脚本需要串联运行,但在任何给定时间都可以运行多个p1、p2和p3

我需要在数十个文件上快速运行这些程序,因此需要使用线程并并行地完成任务

我正在使用python线程、队列和子流程模块来实现这一点

我的问题是,当我的线程数大于1时,程序的行为不稳定,线程不会以可复制的方式相互切换。有时,所有五个线程都能完美地工作并完成

这是我第一次尝试使用线程来做一些事情,我确信这是因为线程通常会遇到一些涉及争用条件的问题。但我想知道我该如何清理我的代码

实际代码位于(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运行程序获取输入文件句柄,然后调用subprocess.call()来运行一个shell脚本,该脚本使用文件输入,并使用run\u p1\u using\u 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()
P1类\u流道(对象):
def uuu init uuu(self,inputfile):
self.my_shell_script=“”#!/usr/bin/sh

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

    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!
线程正在消亡,仅仅是因为它们在准备进行更多操作时没有找到工作

相反,您应该让线程进入睡眠(等待)状态,直到其输入队列上的事件发出信号,只有当编排器(主程序)发出处理完成的信号(设置自杀标志,并向所有队列发出信号)时,才会发出死亡信号

(我看到您已经更改了代码)

@falmari在他其他地方的注释中可能的意思是,你的问题不是关于某个特定的问题(其他人可以回答),因为你的代码中对
线程
库的总体使用是错误的,你对编程语言的使用通常是笨拙的。例如:

  • 调用
    worker.join()
    使主程序在启动P2线程之前按顺序等待所有P1线程的终止,从而阻止任何并发尝试
  • 您应该重写
    Thread.run()
    或提供可调用的构造函数。不需要
    Pn\u runner
  • 所有的线程类都是一样的,每个进程阶段不需要不同的类
  • 如果您已经在使用Python,那么调用外部程序(更不用说shell脚本)是没有意义的,除非您绝对不能用纯Python轻松地完成这项工作
  • 由于上述原因,让您的程序将shell脚本写入文件系统是非常奇怪的,而且几乎肯定是不必要的
为了解决您的这个特殊问题,我建议您:

  • 试着坚持使用100%的Python。如果你做不到,或者看起来太难,你至少已经找到了必须从外部访问的特定功能
  • 构建一个不使用并发的解决方案
  • 测量程序的性能并尝试从算法上改进它
  • 尽可能避免线程化。CPU受限的程序将在没有线程化的情况下占用所有可用周期。磁盘受限太多(或绑定任何外部/远程资源)的程序如果没有其他事情要做,程序将最终等待磁盘。要从线程化中获益,程序必须在计算和外部资源使用之间保持正确的平衡(或者必须能够在请求到达时提供服务,即使在其他情况下很忙)
  • 用python的方式:从简单开始,逐渐增加功能和复杂性,同时始终避免任何看似复杂的事情

  • 如果您打算自学Python中的线程,那么一定要找到一个简单的问题来进行实验。如果您只想并行运行几个shell脚本,那么
    bash
    和其他shell已经有了这方面的规定,您不需要使用Python。

    这真的很糟糕:

    runner.run()
    

    你永远不应该手动调用线程的run方法。你用.start()启动线程。你的代码乱七八糟,这里没有人会费力地找出你的错误。

    不知道如何让线程睡眠。线程是否会自动停留?my_file_to_process=None:sleep(睡眠时间)这就足够了,但太天真了。我自己正在学习Python库,我已经知道你需要什么了。看看互斥标准库。记住线程安全的数据结构不能成为并发监视器。@Falmari,我想你是说我不应该有任何问题。但是我看到了很多iss测试时使用。在运行Python2.6.5的64位ubuntu上,不同的运行有不同的未完成作业,机器有16 GB RAM。更重要的是,代码在OSX、雪豹上运行得非常完美。Python2.6.5只有4 GB RAM和核心duoI,我不知道你的问题是什么,我怀疑RAM大小与此有关。但是队列模块是线程安全的。所以这不是你的问题。@Falmari使用线程安全的数据结构可以保证数据结构不变量得到保留,但不能保证线程会合作解决问题。这不是你的代码。