我有一个字符串列表,我想要定期处理字符串。
开始处理新字符串的时间是1秒,处理字符串需要3秒钟。
我期望观察到的是,从第3秒开始,我将每秒看到一个新结果,直到所有字符串都被处理完毕。
然而,我实际看到的是,当所有结果都生成时,所有结果都显示在一起。 所以问题是,如何修改代码以实现我期望看到的目标?
from twisted.internet import reactor, threads
import json
import time
def process(string):
print "Processing " + string + "\n"
time.sleep(3) # simulate computation time
# write result to file; result is mocked by string*3
file_name = string + ".txt"
with open(file_name, "w") as fp:
json.dump(string*3, fp)
print string + " processed\n"
string_list = ["AAAA", "BBBB", "CCCC", "XXXX", "YYYY", "ZZZZ"]
for s in string_list:
# start a new thread every second
time.sleep(1)
threads.deferToThread(process, s)
reactor.run()
同时,看起来生成结果的顺序与处理字符串的顺序不一致。我猜它只是按顺序打印,但它们实际上是按顺序处理的。如何验证我的猜测?
我注意到的另一个微不足道的事情是Processing YYYY
没有打印在正确的位置。这是为什么? (它与前一个结果之间应该有一个空行。)
Processing AAAA
Processing BBBB
Processing CCCC
Processing XXXX
Processing YYYY
Processing ZZZZ
YYYY processed
CCCC processed
AAAA processed
BBBB processed
XXXX processed
ZZZZ processed
答案 0 :(得分:2)
代码的这一部分是做什么的:
for s in string_list:
# start a new thread every second
time.sleep(1)
threads.deferToThread(process, s)
reactor.run()
计划每个工作块,每个调度操作之间延迟一秒。然后,最后,它启动反应器,允许处理开始。在reactor.run()
之前没有处理。
time.sleep(1)
的使用也意味着您的延迟会受到阻碍,一旦您解决上述问题,这将成为一个问题。
一种解决方案是将for
循环和time.sleep(1)
替换为LoopingCall
。
from twisted.internet.task import LoopingCall, react
string_list = [...]
def process(string):
...
def process_strings(the_strings, f):
def dispatch(s):
d = deferToThread(f, s)
# Add callback / errback to d here to process the
# result or report any problems.
# Do _not_ return `d` though. LoopingCall will
# wait on it before running the next iteration if
# we do.
string_iter = iter(the_strings)
c = LoopingCall(lambda: dispatch(next(string_iter)))
d = c.start(1)
d.addErrback(lambda err: err.trap(StopIteration))
return d
def main(reactor):
return process_strings(string_list, process)
react(main, [])
此代码使用react
来启动和停止反应堆(当Deferred
返回的main
触发时它停止)。它使用以{1}开头的句点在线程池中运行LoopingCall
,直到遇到f(next(string_iter))
(或其他一些错误)。
(StopIteration
和LoopingCall
同时将deferToThread
和*args
传递给他们的可调用对象,如果您愿意(这是样式问题),您也可以写该表达式为**kwargs
。您无法“解包”剩余的lambda,因为这会导致LoopingCall(lambda: deferToThread(f, next(string_iter)))
仅在LoopingCall(deferToThread, f, next(string_iter))
被调用时评估next(string_iter)
,因此您将结束永远处理第一个字符串。)
还有其他可能的调度方法。例如,您可以使用LoopingCall
一次运行3个处理线程 - 一个旧线程完成后立即开始新线程。
cooperate
在这两种情况下,请注意没有使用from twisted.internet.defer import gatherResults
from twisted.internet.task import cooperate
def process_strings(the_strings, f):
# Define a generator of all of the jobs to be accomplished.
work_iter = (
deferToThread(lambda: f(a_string))
for a_string
in the_strings
)
# Consume jobs from the generator in parallel until done.
tasks = list(cooperate(work_iter) for i in range(3))
# Return a Deferred that fires when all three tasks have
# finished consuming all available jobs.
return gatherResults(list(
t.whenDone()
for t
in tasks
))
。