我试图在类的方法中使用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
。
答案 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在这里看到一些代码优化的潜力。