使用Python中的工作池进行异步多处理:如何在超时后继续运行?

时间:2014-01-08 09:43:57

标签: python timeout multiprocessing worker-pool

我想使用一个进程池运行一些作业并应用一个给定的超时,之后应该杀死一个作业,并替换另一个处理下一个任务的作业。

我曾尝试使用multiprocessing模块,它提供了一种异步运行工作池的方法(例如使用map_async),但在那里我只能设置一个“全局”超时,之后所有进程将被杀死。

是否可能有一个单独的超时,之后只会消耗一个花费太长时间的单个进程并且再次向该池添加新工作处理下一个任务和跳过超时的那个)?

这是一个简单的例子来说明我的问题:

def Check(n):
  import time
  if n % 2 == 0: # select some (arbitrary) subset of processes
    print "%d timeout" % n
    while 1:
      # loop forever to simulate some process getting stuck
      pass
  print "%d done" % n
  return 0

from multiprocessing import Pool
pool = Pool(processes=4)
result = pool.map_async(Check, range(10))
print result.get(timeout=1)    

超时后,所有工作人员都被杀死,程序退出。我想继续下一个子任务。我是否必须自己实施此行为或是否存在现有解决方案?

更新

可以杀死悬挂的工人并自动更换。所以我提出了这个代码:

jobs = pool.map_async(Check, range(10))
while 1:
  try:
    print "Waiting for result"
    result = jobs.get(timeout=1)
    break # all clear
  except multiprocessing.TimeoutError: 
    # kill all processes
    for c in multiprocessing.active_children():
      c.terminate()
print result

现在的问题是循环永不退出;即使在处理完所有任务之后,调用get也会产生超时异常。

3 个答案:

答案 0 :(得分:4)

pebble池模块是为解决这些类型的问题而构建的。它支持给定任务的超时,允许检测它们并轻松恢复。

from pebble import ProcessPool
from concurrent.futures import TimeoutError

with ProcessPool() as pool:
    future = pool.schedule(function, args=[1,2], timeout=5)

try:
    result = future.result()
except TimeoutError:
    print "Function took longer than %d seconds" % error.args[1]

对于您的具体示例:

from pebble import ProcessPool
from concurrent.futures import TimeoutError

results = []

with ProcessPool(max_workers=4) as pool:
    future = pool.map(Check, range(10), timeout=5)

    iterator = future.result()

    # iterate over all results, if a computation timed out
    # print it and continue to the next result
    while True:
        try:
            result = next(iterator)
            results.append(result)
        except StopIteration:
            break  
        except TimeoutError as error:
            print "function took longer than %d seconds" % error.args[1] 

print results

答案 1 :(得分:1)

目前,Python没有为工作人员自身以外的池中每个不同任务的控制执行时间提供本机方法 因此,简单的方法是在psutil module中使用wait_procs并将任务实现为子流程。
如果不需要非标准库,则必须在子进程模块的基础上实现自己的池,该模块在主进程中具有工作周期,poll() - 执行每个工作并执行所需的动作。

对于更新的问题,如果您直接终止其中一个工作程序,则池会损坏(这是解释器实现中的错误,因为不应允许此类行为):重新创建工作程序,但任务丢失并且游泳池变得无法加入。 您必须终止所有池,然后再次为其他任务重新创建它:

from multiprocessing import Pool
while True:
    pool = Pool(processes=4)
    jobs = pool.map_async(Check, range(10))
    print "Waiting for result"
    try:
        result = jobs.get(timeout=1)
        break # all clear
    except multiprocessing.TimeoutError: 
        # kill all processes
        pool.terminate()
        pool.join()
print result    

更新

Pebble是一个优秀且方便的库,可以解决这个问题。 Pebble 是为Python函数的异步执行而设计的,其中 PyExPool 是为模块和外部可执行文件的异步执行而设计的,尽管两者都可以互换使用。

另一个方面是当不需要3dparty依赖时,PyExPool可能是一个不错的选择,这是一个多进程执行池的单文件轻量级实现工作和全球超时,将工作分组到任务和其他功能的机会 PyExPool 可以嵌入您的源代码和自定义,具有允许的Apache 2.0许可证和生产质量,用于一个高负载科学基准测试框架的核心。

答案 2 :(得分:0)

尝试使用单独线程上的超时连接每个进程的构造。所以主程序永远不会卡住,以及如果被卡住的进程将因超时而被杀死。该技术是线程和多处理模块的组合。

这是我维护内存中最小x个线程数的方法。它结合了线程和多处理模块。对于其他技术,如受人尊敬的其他成员已经解释过,可能是不寻常的,但可能是值得的。为了便于解释,我采用的方式是一次抓取至少5个网站。

所以这里是: -

#importing dependencies.
from multiprocessing import Process
from threading import Thread
import threading

# Crawler function
def crawler(domain):
    # define crawler technique here.
    output.write(scrapeddata + "\n")
    pass

接下来是threadController函数。此函数将控制到主存储器的线程流。它将继续激活线程以维持threadNum“最小”限制,即。 5.在完成所有活动线程(acitveCount)之前,它不会退出。

它将保持最少的threadNum(5)startProcess函数线程(这些线程最终将从processList启动进程,同时加入时间超过60秒)。在启动threadController之后,会有2个线程不包含在上面的5个限制中。主线程和threadController线程本身。这就是为什么使用threading.activeCount()!= 2。

def threadController():
    print "Thread count before child thread starts is:-", threading.activeCount(), len(processList)
    # staring first thread. This will make the activeCount=3
    Thread(target = startProcess).start()
    # loop while thread List is not empty OR active threads have not finished up.
    while len(processList) != 0 or threading.activeCount() != 2:
        if (threading.activeCount() < (threadNum + 2) and # if count of active threads are less than the Minimum AND
            len(processList) != 0):                            # processList is not empty
                Thread(target = startProcess).start()         # This line would start startThreads function as a seperate thread **

startProcess函数作为一个单独的线程,将从进程列表中启动进程。这个函数的目的(**作为一个不同的线程开始)是它将成为Processes的父线程。因此,当它将以60秒的超时加入它们时,这将阻止startProcess线程向前移动,但这不会阻止threadController执行。所以这样,threadController将按需运行。

def startProcess():
    pr = processList.pop(0)
    pr.start()
    pr.join(60.00) # joining the thread with time out of 60 seconds as a float.

if __name__ == '__main__':
    # a file holding a list of domains
    domains = open("Domains.txt", "r").read().split("\n")
    output = open("test.txt", "a")
    processList = [] # thread list
    threadNum = 5 # number of thread initiated processes to be run at one time

    # making process List
    for r in range(0, len(domains), 1):
        domain = domains[r].strip()
        p = Process(target = crawler, args = (domain,))
        processList.append(p) # making a list of performer threads.

    # starting the threadController as a seperate thread.
    mt = Thread(target = threadController)
    mt.start()
    mt.join() # won't let go next until threadController thread finishes.

    output.close()
    print "Done"

除了在内存中保持最小线程数之外,我的目标是还可以避免内存中的线程或进程被卡住。我是使用超时功能完成的。我为任何打字错误道歉。

我希望这种结构可以帮助这个世界上的任何人。

问候,

Vikas Gautam