![](/img/trans.png)
[英]Python: Wait on all of `concurrent.futures.ThreadPoolExecutor`'s futures
[英]with concurrent.futures.ThreadPoolExecutor() as executor: … does not wait
我試圖在類的方法中使用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 ("Hi")
語句之前看到所有print("!!!!!!")
和print("done")
print ("Hi")
語句。 實際顯示的是!!!!!!!
然后是Hi
,然后是結果字典的KeyError
。
您需要循環concurrent.futures.as_completed()如圖所示這里 。 當每個線程完成時,它將產生值。
您已經發現,游泳池正在等待; 永遠不會執行print('done')
因為可能是TypeError
提早產生了。
池不直接等待任務完成,而是等待其工作線程加入,這隱式地要求一種方式(成功)或另一種方式(異常)執行任務。
您沒有看到引發異常的原因是因為任務包裝在Future
。 Future
封裝了可調用對象的異步執行。
Future
實例由執行者的submit
方法返回,它們允許查詢執行狀態並訪問其結果。
這使我想提出一些意見。
self.token_q
的Queue
似乎不必要
從您共享的代碼來看,您僅使用此隊列將任務結果傳遞回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(例如,套接字上的數據)),那么這並不是什么大問題。 如果您的任務需要在處理器中不斷執行字節碼,則暫停一個線程讓另一個線程執行某些指令沒有任何好處。 實際上,所產生的上下文切換甚至可能被證明是有害的。
您可能希望采用並行而不是並發 。 看一下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
大於五,我期望一個KeyError
在這里提。
如果保證self.zettels
的長度為5,那么我會在這里看到一些代碼優化的潛力。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.