背景
我們知道 Python 中有多線程threading 和多進程multiprocessing 實現並發,
但是這兩個東西開銷很大,一是開啟線程/進程的開銷,二是主程序和子程序之間的通信需要 序列化和反序列化,
所以有些時候需要使用更加高級的用法,然而這些高級用法十分復雜,而且 threading 和 multiprocessing 用法還不一樣。
於是誕生了 concurrent.future
1. 它可以解決大部分的復雜問題 【但並不是全部,如果嘗試后效果不好,還需要使用他們的高級用法】
2. 而且統一了線程和進程的用法
concurrent.future 提供了 ThreadPoolExecutor 和 ProcessPoolExecutor 兩個類,其實是對 線程池和進程池 的進一步抽象,而且具有以下特點:
3. 主程序可以獲取子程序的狀態和返回值
4. 子程序完成時,主程序能立刻知道
效率驗證
求最大公約數,測試數據如下
def gcd(pair): # 最大公約數 a, b = pair low = min(a, b) for i in range(low, 0, -1): if a % i == 0 and b % i == 0: return i numbers = [(1963309, 2265973), (2030677, 3814172), (1551645, 2229620), (2039045, 2020802)]
無並發
sum = 0 for i in range(20): start = time.time() results = list(map(gcd, numbers)) end = time.time() sum += end - start print(sum/20) # 0.6637879729270935
多線程
from concurrent.futures import ThreadPoolExecutor sum = 0 for i in range(20): start = time.time() pool = ThreadPoolExecutor(max_workers=3) results = list(pool.map(gcd, numbers)) end = time.time() sum += end - start print(sum/20) # 0.9184025406837464
分析:由於全局解釋器鎖GIL的存在,多線程無法利用多核CPU進行並行計算,而是只使用了一個核,加上本身的開銷,計算效率更低了。
通過 資源管理器 查看 CPU 使用率:25%左右 【4核,用了一個】
多進程
from concurrent.futures import ProcessPoolExecutor if __name__ == '__main__': sum = 0 for i in range(20): start = time.time() pool = ProcessPoolExecutor(max_workers=3) results = list(pool.map(gcd, numbers)) end = time.time() sum += end - start print(sum/20) # 0.8655495047569275
分析:利用多核CPU並行計算,比多線程快了點,但是由於本身的開銷,還是沒有無並發效率高,
通過 資源管理器 查看 CPU 使用率:75%左右 【4核,用了三個,max_workers=3】
這主要是數據量太小了,體現不出並發的優勢,於是我把數據量稍微加大點
numbers = [(1963309, 2265973), (2030677, 3814172), (1551645, 2229620), (2039045, 2020802)] * 10
重新測試,無並發 7s,多進程 2s,效果明顯提高。
注意,在使用多進程時,必須把 多進程代碼 寫在 if __name__ == '__main__' 下面,否則異常,甚至報錯
concurrent.futures.process.BrokenProcessPool: A process in the process pool was terminated abruptly while the future was running or pending.
小結:多線程不適合計算密集型,適合IO密集型,后面我會驗證,多進程適合計算密集型。
API 用法
具體方法參照參考資料,非常簡單,這里我就不寫了。
參考資料:
https://www.jianshu.com/p/b9b3d66aa0be