定期调用deferToThread

时间:2017-07-02 09:48:11

标签: python twisted

我有一个字符串列表,我想要定期处理字符串。

开始处理新字符串的时间是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

1 个答案:

答案 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))(或其他一些错误)。

StopIterationLoopingCall同时将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 ))