在Python中的多处理池中动态重新排序作业

时间:2013-08-16 07:00:27

标签: python python-2.7 multiprocessing pool

我正在编写一个python脚本(用于cygwin和linux环境),对使用subprocess.Popen()从命令行运行的程序运行回归测试。基本上,我有一组作业,其中一部分需要根据开发人员的需要运行(大约10到1000)。每项工作可能需要几秒到20分钟才能完成。

我的作业在多个处理器上成功运行,但我正试图通过智能地排序作业(基于过去的性能)来节省一些时间,以便首先运行更长的作业。复杂的是,一些工作(稳态计算)需要先于其他工作(基于由稳态确定的初始条件的瞬态)。

我目前处理此问题的方法是在同一进程上递归运行父作业和所有子作业,但某些作业有多个长时间运行的子作业。父作业完成后,我想将子项添加回池中以转移到其他进程,但是需要将它们添加到队列的头部。我不确定我能用多处理来做到这一点.Pool。我查找了Manager的示例,但它们都基于网络似乎,并不是特别适用。任何代码形式的帮助或链接到多处理的好教程(我用Google搜索...)将非常感激。这是我迄今为止所得到的代码的框架,评论指出我希望在其他处理器上生成的子作业。

import multiprocessing
import subprocess

class Job(object):
  def __init__(self, popenArgs, runTime, children)
    self.popenArgs = popenArgs #list to be fed to popen
    self.runTime = runTime #Approximate runTime for the job
    self.children = children #Jobs that require this job to run first

def runJob(job):
  subprocess.Popen(job.popenArgs).wait()
  ####################################################
  #I want to remove this, and instead kick these back to the pool
  for j in job.children: 
    runJob(j)
  ####################################################

def main(jobs):
  # This jobs argument contains only jobs which are ready to be run
  # ie no children, only parent-less jobs
  jobs.sort(key=lambda job: job.runTime, reverse=True)
  multiprocessing.Pool(4).map(runJob, jobs)

1 个答案:

答案 0 :(得分:0)

首先,让我第二个Armin Rigo的评论:没有理由在这里使用多个进程而不是多个线程。在控制过程中,你花费大部分时间等待子过程完成;你没有CPU密集型工作来并行化。

使用线程也可以更轻松地解决您的主要问题。现在,您将作业存储在其他作业的属性中,即隐式依赖图。您需要一个单独的数据结构,根据计划对作业进行排序。此外,每个作业树目前都与一个工作流程相关联。您希望将工作者与用于保存作业的数据结构分离。然后工人们从同一个任务队列中抽取工作;在工人完成工作后,它会将工作的孩子排入队列,然后由任何可用的工作人员处理。

由于您希望在父项完成后将子作业插入行的前面,因此类似堆栈的容器似乎符合您的需求; Queue模块提供了一个可以使用的线程安全的LifoQueue类。

import threading
import subprocess
from Queue import LifoQueue

class Job(object):
  def __init__(self, popenArgs, runTime, children):
    self.popenArgs = popenArgs
    self.runTime = runTime
    self.children = children

def run_jobs(queue):
  while True:
    job = queue.get()
    subprocess.Popen(job.popenArgs).wait()
    for child in job.children: 
      queue.put(child)
    queue.task_done()

# Parameter 'jobs' contains the jobs that have no parent.
def main(jobs):
  job_queue = LifoQueue()
  num_workers = 4
  jobs.sort(key=lambda job: job.runTime)
  for job in jobs:
    job_queue.put(job)
  for i in range(num_workers):
    t = threading.Thread(target=run_jobs, args=(job_queue,))
    t.daemon = True
    t.start()
  job_queue.join()

一些注意事项:(1)我们无法通过监视工作线程来知道所有工作何时完成,因为他们没有跟踪要完成的工作。那是队列的工作。因此,主线程监视队列对象以了解所有工作何时完成(job_queue.join())。因此,我们可以将工作线程标记为守护程序线程,因此只要主线程无需等待工作程序,进程就会退出。因此,我们避免了主线程和工作线程之间的通信需要,以便告诉后者何时突破它们的循环并停止。

(2)我们知道,当所有已入队的任务都已标记为已完成时,所有工作都已完成(具体而言,当task_done()被调用的次数等于已经过的项目数时排队的)。使用队列为空是不可靠的,因为所有工作都已完成;在从中弹出作业并将该作业的孩子排队之前,队列可能会暂时且误导性地为空。