我有工作人员和任务要做:
workers = ['peter', 'paul', 'mary']
tasks = range(13)
现在我想将任务拆分成块或批量工作,这样每个工作人员可以在一个批处理上工作,并且完成与其他人相同的工作量。在我的现实生活中,我想将批处理作业安排到计算场。批处理作业应该并行运行。实际的时间表和调度由商业级工具完成,例如lsf或grid。
我期望的一些例子:
>>> distribute_work(['peter', 'paul', 'mary'], range(3))
[('peter', [0]), ('paul', [1]), ('mary', [2])]
>>> distribute_work(['peter', 'paul', 'mary'], range(6))
[('peter', [0, 3]), ('paul', [1, 4]), ('mary', [2, 5])]
>>> distribute_work(['peter', 'paul', 'mary'], range(5))
[('peter', [0, 3]), ('paul', [1, 4]), ('mary', [2])]
非常相似
不同之处在于我想按顺序或优先顺序使用这些功能:
len
内部没有长数据结构的构建关于要求的一些附注:
我试图绕过itertools
和这个特殊问题,并提出以下代码来说明问题:
from itertools import *
def distribute_work(workers, tasks):
batches = range(len(workers))
return [ ( workers[k],
[t[1] for t in i]
) for (k,i) in groupby(sorted(zip(cycle(batches),
tasks),
key=lambda t: t[0]),
lambda t: t[0]) ]
这满足4.但是这种类型很可能违反了1 ..和2./3。甚至没想过。
可能有一些简单的解决方案,以一种我没想过的方式组合一些stdlib组件。但也许不是。任何人?
答案 0 :(得分:1)
你必须预先批处理吗?
为什么不只是拥有一个队列,并让每个工作人员在完成工作单元时弹出队列?
答案 1 :(得分:1)
def doleOut(queue, workers):
for worker,task in itertools.izip(itertools.cycle(workers),queue):
yield worker,task
只要有队列,这将继续返回(worker, task)
个元组。因此,如果你有阻止waitForMoreWork
,你可以这样做:
queue = []
doler = distribute_work(workers, queue)
while 1:
queue.append(waitForMoreWork)
currentqueuelen = len(queue)
for i in range(0,queuelen):
worker,item = doler.next()
worker.passitem(item)
这样它会阻塞,直到有更多队列项,然后分发它们,然后再次阻止。您可以将waitForMoreWork表达式设置为一次分发尽可能多的项目。
答案 2 :(得分:1)
我认为您希望使用multiprocessing.Pool.imap
来处理您的员工并分配他们的工作。我相信它能做你想做的一切。
jobs = (some generator) # can consume jobs from a generator
pool = multiprocessing.Pool(3) # set number of workers here
results = pool.imap(process_job, jobs) # returns a generator
for r in results: # loop will block until results arrive
do_something(r)
如果结果的顺序与您的应用无关,您也可以使用imap_unordered
。
答案 3 :(得分:0)
好的,在说不可能之后,这是一个想法。也许这是我应该转向代码审查的东西 - 我非常感兴趣的是关于它在内存中产生多少开销的评论。换句话说,我不知道这是否真的解决了任务列表很长且未知大小的问题。
作为Blckknght mentioned multiprocessing
might be the better alternative。
代码:
import itertools
def distribute_work(workers, tasks):
"""Return one generator per worker with a fair share of tasks
Task may be an arbitrary length generator.
Workers should be an iterable.
"""
worker_count = len(workers)
worker_ids = range(worker_count)
all_tasks_for_all_workers = itertools.tee(tasks, worker_count)
assignments = [ (workers[id], itertools.islice(i, id, None, worker_count))
for (id,i) in enumerate(all_tasks_for_all_workers) ]
return(assignments)
算法是
#W
是工作人员数量,则第一个工作人员会执行0
,#W
,2*#W
,3*#W
等任务。第二个工作人员需要{{1} }},0+1
,#W+1
,2*#W+1
等。每个工作人员的拼接都可以使用3*#W+1
对于纯粹的拆分/分配任务,此功能并不真正需要工人的名称。但是工人的数量是多少。更改此功能将使该功能更加通用和有用,并使返回值更容易理解。要回答我自己的问题,我将按原样保留该功能。
用法和结果:
itertools.islice
它还处理工人具有相同名称但不同实体的情况:
>>> for (worker,tasks) in distribute_work(['peter', 'paul', 'mary'], range(5)):
... print(worker, list(tasks))
...
peter [0, 3]
paul [1, 4]
mary [2]
答案 4 :(得分:0)
这是我喜欢的方法:
parallelism = os.cpu_count()
num_todos = len(todos)
# this zip fanciness makes each chunk stripe through the data sequentially overall so that the
# first items still get done first across all the workers
chunksize = math.ceil(num_todos / parallelism)
chunks = list(itertools.zip_longest(*[todos[i:i+chunksize] for i in range(0, num_todos, chunksize)]))
chunks = [[c for c in chunk if c is not None] for chunk in chunks]
with Pool(processes=parallelism) as pool:
tasks = [pool.apply_async(my_function, args=(chunk)) for chunk in chunks]
[task.get() for task in tasks]
根据您是否需要累积结果,您可以进行调整,但对我来说有趣的部分是让工作人员协作以全局顺序完成工作(在我的情况下,处理连续的图像帧以便我可以看到事情看起来像所有的cpus都在发动。)