假设我有以下多处理结构:
import multiprocessing as mp
def worker(working_queue, output_queue):
while True:
if working_queue.empty() == True:
break
else:
picked = working_queue.get()
res_item ="Number" + str(picked)
output_queue.put(res_item)
return
if __name__ == '__main__':
static_input = xrange(100)
working_q = mp.Queue()
output_q = mp.Queue()
results_bank = []
for i in static_input:
working_q.put(i)
processes = [mp.Process(target=worker,args=(working_q, output_q)) for i in range(2)]
for proc in processes:
proc.start()
for proc in processes:
proc.join()
results_bank = []
while True:
if output_q.empty() == True:
break
results_bank.append(output_q.get_nowait())
if len(results_bank) == len(static_input):
print"Good run"
else:
print"Bad run"
我的问题:当working_queue仍在"工作"(或至少,未完成)时,我如何'批量'将我的结果写入单个文件?
注意:我的实际数据结构对于相对于输入的无序结果不敏感(尽管我的示例使用整数)。
此外,我认为从输出队列写入批处理/集合是最佳实践,而不是从不断增长的结果库对象。 但是,我对依赖这两种方法的解决方案持开放态度。 我是多处理的新手,因此对这个问题不了解最佳实践或最有效的解决方案。
@martineau感谢澄清'批次'。我自己也要做同样的事。
好像你可以在worker()中有一个嵌套循环,直到working_queue被清空。那是"批量写"的意思吗?
不,我的数据需求更多"因为结果累积,写出来"。比如说,在5个'集'中写出结果。根据我对你的建议的理解,这将在work_queue结束时写出结果,这基本上等同于我的'成长对象'(结果库)在上面的例子中。或者你的意思是建议当work_queue'清除'或'刷新'时,我可以写出结果集?
我的意思是后者(当时通过获取所有内容来刷新它)。当你累积了批次的价值时,你可以把它们写出来。我认为这基本上是@Messa在他的回答中提出的建议。
哦,好吧。正如我所说,我是多处理的新手,所以有点不确定队列是如何工作的。我知道将结果放在专用队列中但是如果我尝试从输出队列中写入仍然填充,则不知道进程是否会"堵塞"或相互竞争。
如果您希望使用mp.Process es和mp.Queue,则可以采用批量处理结果的方法。主要思想是在writer函数中,如下:
import itertools as IT
import multiprocessing as mp
SENTINEL = None
static_len = 100
def worker(working_queue, output_queue):
for picked in iter(working_queue.get, SENTINEL):
res_item ="Number {:2d}".format(picked)
output_queue.put(res_item)
def writer(output_queue, threshold=10):
result_length = 0
items = iter(output_queue.get, SENTINEL)
for batch in iter(lambda: list(IT.islice(items, threshold)), []):
print('
'.join(batch))
result_length += len(batch)
state = 'Good run' if result_length == static_len else 'Bad run'
print(state)
if __name__ == '__main__':
num_workers = 2
static_input = range(static_len)
working_q = mp.Queue()
output_q = mp.Queue()
writer_proc = mp.Process(target=writer, args=(output_q,))
writer_proc.start()
for i in static_input:
working_q.put(i)
processes = [mp.Process(target=worker, args=(working_q, output_q))
for i in range(num_workers)]
for proc in processes:
proc.start()
# Put SENTINELs in the Queue to tell the workers to exit their for-loop
working_q.put(SENTINEL)
for proc in processes:
proc.join()
output_q.put(SENTINEL)
writer_proc.join()
当传递两个参数时,iter需要一个可调用的和一个标记:
iter(callable, sentinel)。重复调用可调用函数(即函数),直到它返回等于sentinel的值。所以
items = iter(output_queue.get, SENTINEL)
将items定义为可迭代的,当迭代时,将返回output_queue中的项
直到output_queue.get()返回sentinel。
for-loop:
for batch in iter(lambda: list(IT.islice(items, threshold)), []):
重复调用lambda函数,直到返回一个空列表。调用时,lambda函数返回可迭代items中最多threshold个项目的列表。因此,这是"没有填充的n个项目分组"的习语。有关此成语的更多信息,请参阅此帖子。
请注意,测试working_q.empty()不是一个好习惯。这可能会导致竞争状况。例如,假设当working_q中只剩下1个项目时,我们在这些行上有2个worker进程:
def worker(working_queue, output_queue):
while True:
if working_queue.empty() == True:
break
else:
picked = working_queue.get()
res_item ="Number" + str(picked)
output_queue.put(res_item)
return
假设Process-1调用working_queue.empty(),而队列中仍有一个项目。所以它返回False。然后Process-2调用working_queue.get()并获取最后一项。然后Process-1到达picked = working_queue.get()行并挂起,因为队列中没有其他项目。
因此,当a for-loop时,使用标记(如上所示)具体地发出信号
或while-loop应该停止而不是检查queue.empty()。
你能否进一步解释你的代码从'for res_item in iter(output_queue.get,SENTINEL)开始:'特别是,我不确定为什么(或如何)'if len(batch)> = threshold'语句是"如果len(批处理)"中的"重复"...无论是否达到阈值,您似乎都在扩展结果对象?无论阈值,批次等都打印都很好,但是了解'批处理'长度何时等于阈值以便用写入文件替换'print'语句是至关重要的。
@DVHughes:我在上面添加了一些解释。如果有什么不清楚,请告诉我。
好的!谢谢,我认为这是你提到的'剩余'批次。只是想在添加任何写语句之前如果len(批处理)看起来你正在测试批次不是空的..但实际上你正在测试是否有剩余/遗留的东西。相同的区别,但在概念上是清楚的,特别是对于其他人,因为它们发生在这个问题/线程中。谢谢!在我的示例代码中,这非常容易理解并与我基于队列的多进程方法一致。
@DVHughes:我已经改变了上面的代码,以便我们迭代批量而不是迭代单个项目。这避免了剩余批次的问题和(大奖金)意味着您不必重复打印到屏幕或写入文件两次的代码。
谢谢,这是非常干净的逻辑,从概念上可以清楚地看到代码。我认为你改善了答案。我能在'批量获取'上找到的唯一其他资源就是这样的:stackoverflow.com/questions/41498614/
没有像"batch q.get"这样的操作。但是,一个接一个地放置/弹出一批项目而不是项目是一个好习惯。
这正是multiprocessing.Pool.map正在使用其参数chunksize :)
为了尽快写输出,有Pool.imap_unordered,它返回一个iterable而不是list。
def work(item):
return"Number" + str(item)
import multiprocessing
static_input = range(100)
chunksize = 10
with multiprocessing.Pool() as pool:
for out in pool.imap_unordered(work, static_input, chunksize):
print(out)