与并发.futures.ThreadPoolExecutor()作为执行者:...不等待

时间:2019-07-04 03:13:33

标签: python threadpool wait threadpoolexecutor concurrent.futures

我试图在类的方法中使用ThreadPoolExecutor()来创建线程池,这些线程将在同一类中执行另一个方法。我有with concurrent.futures.ThreadPoolExecutor()...,但是它没有等待,并且抛出一个错误,说我在“ with ...”语句之后查询的字典中没有键。我了解为什么会引发错误,因为尚未更新字典,因为池中的线程尚未完成执行。我知道线程没有完成执行,因为在ThreadPoolExecutor内调用的方法中有一个print(“ done”),并且“ done”未打印到控制台。

我是线程的新手,因此,如果有关于如何更好地做到这一点的建议,将不胜感激!

    def tokenizer(self):
        all_tokens = []
        self.token_q = Queue()
        with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
            for num in range(5):
                executor.submit(self.get_tokens, num)
            executor.shutdown(wait=True)

        print("Hi")
        results = {}
        while not self.token_q.empty():
            temp_result = self.token_q.get()
            results[temp_result[1]] = temp_result[0]
            print(temp_result[1])
        for index in range(len(self.zettels)):
            for zettel in results[index]:
                all_tokens.append(zettel)
        return all_tokens

    def get_tokens(self, thread_index):
        print("!!!!!!!")
        switch = {
            0: self.zettels[:(len(self.zettels)/5)],
            1: self.zettels[(len(self.zettels)/5): (len(self.zettels)/5)*2],
            2: self.zettels[(len(self.zettels)/5)*2: (len(self.zettels)/5)*3],
            3: self.zettels[(len(self.zettels)/5)*3: (len(self.zettels)/5)*4],
            4: self.zettels[(len(self.zettels)/5)*4: (len(self.zettels)/5)*5],
        }
        new_tokens = []
        for zettel in switch.get(thread_index):
            tokens = re.split('\W+', str(zettel))
            tokens = list(filter(None, tokens))
            new_tokens.append(tokens)
        print("done")
        self.token_q.put([new_tokens, thread_index])

'''

预期在print("!!!!!!")语句之前查看所有print("done")print ("Hi")语句。 实际上显示结果字典的!!!!!!!,然后显示Hi,然后显示KeyError

2 个答案:

答案 0 :(得分:0)

您需要如here所示循环并发.futures.as_completed()。当每个线程完成时,它将产生值。

答案 1 :(得分:0)

您已经发现,池 正在等待; print('done')从未执行过,因为大概是TypeError提早了。
池不直接等待任务完成,而是等待其工作线程加入,这隐式地要求一种方式(成功)或另一种方式(例外)执行任务以完成。

之所以看不到引发异常的原因是因为任务被包装在Future中。 Future

  

[...]封装了可调用对象的异步执行。

Future实例由执行者的submit方法返回,它们允许查询执行状态并访问其结果。

这使我想讲一些话。

Queue中的self.token_q似乎不必要
从您共享的代码来看,您仅使用此队列将任务结果传递回tokenizer函数。不需要这样做,您可以从Future访问submit的调用返回的值:

def tokenizer(self):
    all_tokens = []
    with ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(get_tokens, num) for num in range(5)]
        # executor.shutdown(wait=True) here is redundant, it is called when exiting the context:
        # https://github.com/python/cpython/blob/3.7/Lib/concurrent/futures/_base.py#L623

    print("Hi")
    results = {}
    for fut in futures:
        try:
            res = fut.result()
            results[res[1]] = res[0]
        except Exception:
            continue
    [...] 

def get_tokens(self, thread_index):
    [...]
    # instead of self.token_q.put([new_tokens, thread_index])
    return new_tokens, thread_index

您的程序可能无法从使用线程中受益
从您共享的代码来看,get_tokens中的操作似乎受CPU约束,而不是I / O约束。如果您正在CPython中运行程序(或使用Global Interpreter Lock的任何其他解释器),则在这种情况下使用线程将没有任何好处。

  

在CPython中,全局解释器锁 GIL 是一种互斥体,可以保护对Python对象的访问,从而防止多个线程一次执行Python字节码。

这意味着对于任何Python进程,在任何给定时间只能执行一个线程。如果您手头的任务受I / O约束,即经常暂停以等待I / O(例如套接字上的数据),那么这并不是什么大问题。如果您的任务需要在处理器中不断执行字节码,则暂停一个线程让另一个线程执行某些指令没有任何好处。实际上,由此产生的上下文切换甚至可能被证明是有害的。
您可能想要去parallelism instead of concurrency。看一下ProcessPoolExecutor
但是,我建议对按顺序,并发和并行运行的代码进行基准测试。创建进程或线程是有代价的,并且根据要完成的任务,这样做可能比仅以顺序方式执行一项任务要花更长的时间。


顺便说一句,这看起来有点可疑:

for index in range(len(self.zettels)):
    for zettel in results[index]:
        all_tokens.append(zettel)

results似乎总是有五个项目,因为for num in range(5)。如果self.zettels的长度大于5,我希望这里有一个KeyError
如果保证self.zettels的长度为5,则我d在这里看到一些代码优化的潜力。