concurrent.futures 是標準庫裏的一個模塊,它提供了一個實現異步任務的高級 API 接口。本文將通過一些代碼例子來介紹這個模塊常見的用法。
Executors
Executor 是一個抽象類,它有兩個非常有用的子類–ThreadPoolExecutor 和 ProcessPoolExecutor 。從命名就可以知道,前者採用的是多線程,而後者使用多進程。
下面將分別介紹這兩個子類,在給出的例子中,我們都會創建一個線程池或者進程池,然後將任務提交到這個池子,這個池子將會分配可用的資源(線程或者進程)來執行給定的任務。
ThreadPoolExecutor
首先,先看看代碼:
from concurrent.futures import ThreadPoolExecutor
from time import sleep
# 定義需要執行的任務--休眠5秒後返回傳入的信息
def return_after_5_secs(message):
sleep(5)
return message
# 建立一個線程池,大小爲 3
pool = ThreadPoolExecutor(3)
future = pool.submit(return_after_5_secs, ("hello"))
print(future.done())
sleep(5)
print(future.done())
print(future.result())
輸出結果:
False
False
hello
這個代碼中首先創建了一個 ThreadPoolExecutor 對象–pool ,通常這裏默認線程數量是 20,但我們指定線程池的線程數量是 3。接着就是調用 submit() 方法來把需要執行的任務,也就是函數,以及需要傳給這個函數的參數,然後會得到 Future 對象,這裏調用其方法 done() 用於告訴我們是否執行完任務,是,就返回 true ,沒有就返回 false 。
在上述例子中,第一次調用 done() 時候,並沒有經過 5 秒,所以會得到 false;之後進行休眠 5 秒後,任務就會完成,再次調用 done() 就會得到 true 的結果。如果是希望得到任務的結果,可以調用 future 的result 方法。
對 Future 對象的理解有助於理解和實現異步編程,因此非常建議好好看看官方文檔的介紹:https://docs.python.org/3/library/concurrent.futures.html
ProcessPoolExecutor
ProcessPoolExecutor 也是有相似的接口,使用方法也是類似的,代碼例子如下所示:
from concurrent.futures import ProcessPoolExecutor
from time import sleep
def return_after_5_secs(message):
sleep(5)
return message
pool = ProcessPoolExecutor(3)
future = pool.submit(return_after_5_secs, ("hello"))
print(future.done())
sleep(5)
print(future.done())
print("Result: " + future.result())
輸出結果:
False
False
Result: hello
通常,我們會用多進程 ProcessPoolExecutor 來處理 CPU 密集型任務,多線程 ThreadPoolExecutor 則更適合處理網絡密集型 或者 I/O 任務。
儘管這兩個模塊的接口相似,但 ProcessPoolExecutor 採用的是 multiprocessing 模塊,並且不會被 GIL( Global Interpreter Lock) 所影響。不過對於這個模塊,我們需要注意不能採用任何不能序列化的對象。
Executor.map()
上述兩個模塊都有一個共同的方法–map()。跟 Python 內建的 map 函數類似,該方法可以實現對提供的一個函數進行多次調用,並且通過給定一個可迭代的對象來將每個參數都逐一傳給這個函數。另外,採用 map() 方法,提供的函數將是併發調用。
對於多進程,傳入的可迭代對象將分成多塊的數據,每塊數據分配給每個進程。分塊的數量可以通過調整參數 chunk_size ,默認是 1.
下面是官方文檔給出的 ThreadPoolExecutor 的例子:
import concurrent.futures
import urllib.request
URLS = ['http://www.baidu.com/',
'http://www.163.com/',
'http://www.126.com/',
'http://www.jianshu.com/',
'http://news.sohu.com/']
# Retrieve a single page and report the url and contents
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# We can use a with statement to ensure threads are cleaned up promptly
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)))
輸出結果:
'http://www.baidu.com/' page is 153759 bytes
'http://www.163.com/' page is 693614 bytes
'http://news.sohu.com/' page is 175707 bytes
'http://www.126.com/' page is 10521 bytes
'http://www.jianshu.com/' generated an exception: HTTP Error 403: Forbidden
而對於 ProcessPoolExecutor ,代碼如下所示:
import concurrent.futures
import math
PRIMES = [
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419]
def is_prime(n):
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()
輸出結果:
112272535095293 is prime: True
112582705942171 is prime: True
112272535095293 is prime: True
115280095190773 is prime: True
115797848077099 is prime: True
1099726899285419 is prime: False
as_completed() & wait()
concurrent.futures 模塊中有兩個函數用於處理進過 executors 返回的 futures,分別是 as_completed() 和 wait()。
as_completed() 函數會獲取 Future 對象,並且隨着任務開始處理而返回任務的結果,也就是需要執行的函數的返回結果。它和上述介紹的 map() 的主要區別是 map() 方法返回的結果是按照我們傳入的可迭代對象中的順序返回的。而 as_completed() 返回的結果順序則是按照任務完成的順序,哪個任務先完成,先返回結果。
下面給出一個例子:
from concurrent.futures import ThreadPoolExecutor, wait, as_completed
from time import sleep
from random import randint
def return_after_5_secs(num):
sleep(randint(1, 5))
return "Return of {}".format(num)
pool = ThreadPoolExecutor(5)
futures = []
for x in range(5):
futures.append(pool.submit(return_after_5_secs, x))
for x in as_completed(futures):
print(x.result())
輸出結果
Return of 3
Return of 4
Return of 0
Return of 2
Return of 1
wait() 函數返回一個包含兩個集合的帶有名字的 tuple,一個集合包含已經完成任務的結果(任務結果或者異常),另一個包含的就是還未執行完畢的任務。
同樣,下面是一個例子:
from concurrent.futures import ThreadPoolExecutor, wait, as_completed
from time import sleep
from random import randint
def return_after_5_secs(num):
sleep(randint(1, 5))
return "Return of {}".format(num)
pool = ThreadPoolExecutor(5)
futures = []
for x in range(5):
futures.append(pool.submit(return_after_5_secs, x))
print(wait(futures))
輸出結果:
DoneAndNotDoneFutures(done={<Future at 0x2474aa4fba8 state=finished returned str>, <Future at 0x2474a903048 state=finished returned str>, <Future at 0x2474aa4fa58 state=finished returned str>, <Future at 0x2474aa4fcf8 state=finished returned str>, <Future at 0x2474a8beda0 state=finished returned str>}, not_done=set())
我們可以通過指定參數來控制 wait() 函數返回結果的時間,這個參數是 return_when,可選數值有:FIRST_COMPLETED, FIRST_EXCEPTION 和 ALL_COMPLETED。默認結果是 ALL_COMPLETED ,也就是它會等待所有任務都執行完成才返回結果。
via:https://mp.weixin.qq.com/s/ih459d2YW-rlLY3lHqKLKw