摘要:
本文主要是對 Python3 標準庫 concurrent.futures 文檔的翻譯
concurrent.futures 模塊爲異步執行可調用的對象提供了一個高級的接口。
異步執行可以通過線程來實現,使用 ThreadPoolExecutor 模塊,或者使用 ProcessPoolExecutor 模塊通過分離進程來實現。兩種實現都有同樣的接口,他們都是通過抽象類 Executor 來定義的。
Executor 對象
class concurrent.futures.Executor
這是一個抽象類,用來提供方法去支持異步地執行調用,它不應該被直接調用,而是應該通過具體的子類來使用。
submit(fn, *args, **kwargs)
可調用對象的調度器,
fn
參數將會以fn(*args, **kwargs)
的形式來調用,同時返回一個 Future 對象代表了可調用對象的執行情況。“`python
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(pow, 323, 1235)
print(future.result())
> `map(func, *iterables, timeout=None, chunksize=1)`
>> 和`map(func, *iterables)`函數的作用基本相同,除了`func`是被異步執行的,而且幾個對於`func`調用可能是同時執行的。這個函數返回的迭代器調用`__next__()`方法的時候,如果在`timeout`秒內結果不可用,那麼迭代器將會從原始調用的函數向`Executor.map()`拋出一個`concurrent.futures.TimeoutError`的異常。`timeout`既能是一個整數,也能是一個浮點數。如果`timeout`沒有指定的話或者等於 None 的話,那麼等待時間就沒有限制。如果調用函數拋出了一個異常,那麼當迭代器取到這個函數的時候,異常將會被拋出。
>> 當使用`ProcessPoolExecutor`的時候,這個方法將`iterables`切成許多塊,然後將這些內容作爲分離的任務提交到進程池中。每個塊的大概的尺寸能夠通過`chunksize`(大於0的正整數)的參數來指定。當`iterables`非常大的時候,和`chunksize`默認等於1相比,將`chunksize`設置爲一個很大的值,將會顯著地提升性能。在使用`ThreadPoolExecutor`的情況下,`chunksize`的大小沒有影響。
>> Python 3.5新增功能:添加了`chunksize`參數
> `shutdown(wait=True)`
>> 告訴執行器,噹噹前阻塞的 futures 執行完了以後,它應該釋放所有它使用的資源。在`shutdown`函數之後再來調用`Executor.submit()`和`Executor.map()`將會拋出`RuntimeError`
>> 如果`wait`等於 True 的話,這個方法不會立即返回,而直到所有阻塞的 futures 都返回,而且和這個執行器所有相關的資源都被釋放以後,這個函數纔會返回。 如果`wait`設置爲 False ,那麼這個方法會立刻返回,而和這個執行器所有相關的資源只有等到所有阻塞的 futures 都執行完以後纔會被釋放。而無論`wait`參數的值是什麼,整個 Python 程序都會等到所有阻塞的 futures 執行完畢以後纔會退出。
>> 通過`with`語句,可以避免明確地來調用這個方法,它在執行完以後將會自動關閉`Executor`。(調用 Executor.shutdown() 時`wait`會被設置爲True,這將會等待所有 future 執行完畢)
>> ```python
import shutil
with ThreadPoolExecutor(max_workers=4) as e:
e.submit(shutil.copy, 'src1.txt', 'dest1.txt')
e.submit(shutil.copy, 'src2.txt', 'dest2.txt')
e.submit(shutil.copy, 'src3.txt', 'dest3.txt')
e.submit(shutil.copy, 'src4.txt', 'dest4.txt')
ThreadPoolExecutor
ThreadPoolExecutor
是Executor
的子類,使用一個線程池去異步地執行調用。
當一個 Future 關聯的調用等待另外一個 Future 的執行結果的時候,死鎖就有可能發生,例如下面的例子:
import time
def wait_on_b():
time.sleep(5)
print(b.result()) # b 永遠不會完成,因爲它等待着 a 的結果
return 5
def wait_on_a():
time.sleep(5)
print(a.result()) # a 永遠不會完成,因爲它等待着 b 的結果
return 6
executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)
和這個例子:
def wait_on_future():
f = executor.submit(pow, 5, 2)
# 這個也永遠不會完成,因爲線程池裏面最多只能有一個線程,而它現在正在執行着這個函數。
print(f.result())
executor = ThreadPoolExecutor(max_workers=1)
executor.submit(wait_on_future)
class concurrent.futures.ThreadPoolExecutor(max_workers=None)
一個
Executor
的子類,使用線程池中最多max_workers
個線程去異步地執行回調。
Python 3.5中的改變:如果max_workers
參數爲None或者沒有給定,那麼它將會被默認設置成爲機器的CPU核數乘5。這裏假設ThreadPoolExecutor
經常被用來執行IO密集型的工作而不是CPU密集型的工作,工作者的個數應該比ProcessPoolExecutor
的工作者的個數要多。
ThreadPoolExecutor 例子
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
# 獲取一個單頁,同時報告URL和內容
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# 我們可以通過with語句來確保線程能夠被及時地清理
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Start the load operations and mark each future with its URL
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
ProcessPoolExecutor
ProcessPoolExecutor
類是Executor
的一個子類,它使用一個進程池來異步地執行調用。ProcessPoolExecutor
使用multiprocessing
模塊,它允許去避免全局解釋器鎖,但同時也意味着僅僅只有pickable
(TODO譯者注:這裏這個單詞的含義我還沒理解)對象能夠被執行和返回。
__main__
模塊必須能夠被作爲工作者的子模塊導入。這意味着ProcessPoolExecutor
將不會在交互式的解釋器中工作。
從一個已添加到Executor中的可調用對象中調用Executor
或者Future
的方法將會導致死鎖(TODO譯者注:有待實踐操作)。
class concurrent.futures.ProcessPoolExecutor(max_workers=None)
一個
Executor
的子類來異步地執行調用,最多將會使用進程池中max_workers
個工作進程。如果max_workers
是None
或者沒有給出的話。它默認將會使用機器CPU的個數來作爲最大進程數的值。如果max_workers
小於或者等於0,那麼將會拋出一個ValueError
3.3版本中的改變:當一個工作進程被突然終止了後,將會拋出一個BrokenProcessPool
的錯誤。以前的情況是,行爲是未定義的但是 Executor 上的操作或者他自己的 future 將會被凍結或者導致死鎖。
ProcessPoolExecutor 的例子
import concurrent.futures
import math
PRIMES = [
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419]
def is_prime(n):
"""(譯者注) 判斷素數的程序
這裏對 sqrt_n 做一點解釋:
假設 n 不是素數,那麼有 n = x * y( x != 1 and y != 1)
因爲 n = x * y, 所以 x <= sqrt(n) or y <= sqrt(n)
所以 i in [2, sqrt(n)]; i表示能夠被 n 整除的數
所以如果 i not in [2, sqrt(n)]; 那麼n是素數
在這個程序中我們在一開始就判斷過2,所以循環從3開始,且跳過所有偶數
"""
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
Future 對象
Future 類封裝了一個可調用對象的異步執行過程,Future 對象是通過Executor.submit()
函數來創建的。
class concurrent.futures.Future
封裝了一個可調用對象的異步執行過程。Future 實例是通過
Executor.submit()
方法來創建的,而且不應該被直接創建,除非用來測試。
cancel()
嘗試去取消相關回調,如果這個回調正在被執行,而且不能被取消,那麼這個方法將會返回
False
,否則這個方法將會取消相應的回調並且返回True
cancelled()
如果相關回調被成功取消了,那麼這個方法將會返回
True
running()
如果相關回調當前正在被執行而且無法取消,那麼將會返回
True
done()
如果相關的回調被成功地取消或者已經運行完畢那麼將返回
True
result(timeout=None)
返回由相關回調產生的結果。如果這個回調還沒有被完成那麼這個方法將會等待
timeout
秒。如果這個回調在timeout
秒內還沒有返回,一個concurrent.futures.TimeoutError
的異常將會被拋出。timeout
能夠被設置成一個整數或者一個浮點數。如果timeout
沒有被設置或者其值爲None
,那麼等待時間將沒有限制。如果這個 future 在完成之前被取消了,那麼將會拋出一個
CancelledError
的異常。
如果相關的回調拋出了一個異常,那麼這個方法也會相應地拋出這個異常。
exception(timeout=None)
返回由相關回調拋出的異常。如果相關回調還沒有被完成那麼這個方法將會等待
timeout
秒。如果相關回調在timeout
秒內還沒有被完成,那麼將會拋出一個concurrent.futures.TimeoutError
的異常。timeout
能夠被設置成一個整數或者一個浮點數。如果timeout
沒有被設置或者其值爲None
,那麼等待時間將沒有限制。如果這個 future 在完成之前被取消了,那麼將會拋出一個
CancelledError
的異常。
如果相關回調被完成了且沒有拋出異常,None將會被返回。
add_done_callback(fn)
將可調用對象
fn
連接到這個 future 上,fn
將會在 future 被取消或者結束運行時被調用,而且僅有相關 future 這一個參數。
添加的可調用對象將會以它們被添加的順序來調用,而且總是在添加它們的那個進程的所屬的線程中調用(譯者注,可以參考這段代碼)。如果相關調用fn
拋出了一個Exception
子類的異常,它將會被記錄和忽略。如果相關調用fn
拋出了一個BaseException
子類的異常,那麼行爲是未定義的。
如果相關的 future 已經被完成了或者取消了,fn
將會被立刻調用。如下的
Future
方法意味着可以在單元測試或者Exectuor
的實現中使用。
set_running_or_notify_cancel()
這個方法應該僅能夠被
Exectuor
的實現(在執行和Future
相關的工作之前調用)和單元測試調用。
如果這個方法返回False
,那麼相關的Future被取消了。即Future.cancel()
被調用了而且返回True
。任何等待 Future 完成的線程(即通過as_completed
或者wait()
方法等待)都會被喚醒。
這個方法僅能被調用一次,而且不能在Future.set_result()
或者Future.set_exception()
被調用了之後調用。
set_result(result)
將 future 相關的工作的結果設置成
result
這個方法僅僅應該被Exectuor
實現的時候和單元測試來調用。
set_exception(exception)
將 future 相關的工作的結果設置成異常
exception
這個方法僅僅應該被Exectuor
實現的時候和單元測試來調用。
模塊方法
concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED)
等待由
fs
(譯者注:這裏這個fs
表示 futures 的意思)給出的 future 實例(可能由不同的 Exectuor 實例創建的)去完成。返回一個命名二元組的集合。在第一個集合元素中,命名爲done
,包含着那些在wait
函數完成之前就已經完成(被完成或者被取消)的 future。第二個集合元素命名爲not_done
,包含那些未完成的 future。
timeout
用來控制wait
函數在返回之前等待的最大秒數。timeout
能夠被設置成整數或者浮點數。如果timeout
沒有被設置或者值爲None
,那麼等待時間將沒有限制。
return_when
代表了函數應該在什麼時候返回,它必須被設置成如下值中的一個:
Constant |
Description |
---|---|
FIRST_COMPLETED | 當任何 future 結束或者被取消的時候,這個函數就會返回 |
FIRST_EXCEPTION | 當任何 future 通過拋出異常來結束的時候,這個函數就會返回,如果沒有任何函數拋出異常,那麼它等價與ALL_COMPLETED |
ALL_COMPLETED | 當所有的 future 都已結束或者被取消的時候,這個函數就會返回。 |
concurrent.futures.as_completed(fs, timeout=None)
返回一個在由
fs
給出的 future 實例(可能由不同的Executor創建的)上的迭代器,當 future 完成的時候(結束或者被取消),就把這個 futureyield
回去。如果fs
中給出的 future 重複了,那麼將僅會被返回一次。任何在as_completed
函數調用之前就已經完成或者被取消的 future, 將會首先yield
回來。如果__next__()
被調用,在timeout
秒後結果依然不可達,那麼返回的迭代器將會從原始的調用向as_completed
拋出一個concurrent.futures.TimeoutError
異常。timeout
可以被設置成整數或者浮點數。如果timeout
沒有被指定或者其值爲None
,那麼等待的時間將沒有限制。See also:
PEP 3148 - futures - 執行異步計算
這個提案描述了包含在 Python 標準庫中的這個特性。
異常類
exception
concurrent.futures.CancelledError
當 future 被取消的時候拋出這個異常。
exception
concurrent.futures.TimeoutError
當 future 的操作超出給定的
timeout
時間的時候拋出這個異常。exception
concurrent.futures.process.BrokenProcessPool
派生自
RuntimeError
,當ProcessPoolExecutor
中的一個工作進程以不乾淨的方式(例如,它是從外部被殺死的)結束的時候,這個異常將會拋出。
Python 3.3 後的新特性