當有多個 IO 密集型的任務要被處理時,我們自然而然會想到多線程.但如果任務非常多,我們不可能每一個任務都啟動一個線程去處理,這個時候最好的辦法就是實現一個線程池,至於池子裡面的線程數量可以根據業務場景進行設置.
比如我們實現一個有 10 個線程的線程池,這樣可以並發地處理 10 個任務,每個線程將任務執行完之後,便去執行下一個任務.通過使用線程池,可以避免因線程創建過多而導致資源耗盡,而且任務在執行時的生命周期也可以很好地把控.
而線程池的實現方式也很簡單,但這裡我們不打算手動實現,因為 Python 提供了一個標准庫 concurrent.futures,已經內置了對線程池的支持.所以本篇文章,我們就來詳細介紹一下該模塊的用法.
Future 對象
當我們往線程池裡面提交一個函數時,會分配一個線程去執行,同時立即返回一個 Future 對象.通過 Future 對象可以監控函數的執行狀態,有沒有出現異常,以及有沒有執行完畢等等.如果函數執行完畢,內部便會調用 future.set_result 將返回值設置到 future 裡面,然後外界便可調用 future.result 拿到返回值.
除此之外 future 還可以綁定回調,一旦函數執行完畢,就會以 future 為參數,自動觸發回調.所以 future 被稱為未來對象,可以把它理解為函數的一個容器,當我們往線程池提交一個函數時,會立即創建相應的 future 然後返回.函數的執行狀態什麼的,都通過 future 來查看,當然也可以給它綁定一個回調,在函數執行完畢時自動觸發.
那麼下面我們就來看一下 future 的用法,文字的話理解起來可能有點枯燥.
需要注意的是:只能執行一次 set_result,但是可以多次調用 result 獲取結果.
執行 future.result() 之前一定要先 set_result,否則會一直處於阻塞狀態.當然 result 方法還可以接收一個 timeout 參數,表示超時時間,如果在指定時間內沒有獲取到值就會拋出異常.
提交函數自動創建 Future 對象
我們上面是手動創建的 Future 對象,但工作中很少會手動創建.我們將函數提交到線程池裡面運行的時候,會自動創建 Future 對象並返回.這個 Future 對象裡面就包含了函數的執行狀態,比如此時是處於暫停、運行中還是完成等等,並且函數在執行完畢之後,還會調用 future.set_result 將自身的返回值設置進去.
這裡再強調一下 future.result(),這一步是會阻塞的,舉個例子:
可以看到,future.result() 這一步花了將近 3s.其實也不難理解,future.result() 是干嘛的?就是為了獲取函數的返回值,可函數都還沒有執行完畢,它又從哪裡獲取呢?所以只能先等待函數執行完畢,將返回值通過 set_result 設置到 future 裡面之後,外界才能調用 future.result() 獲取到值.
如果不想一直等待的話,那麼在獲取值的時候可以傳入一個超時時間.
當然啦,這麼做其實還不夠智能,因為我們不知道函數什麼時候執行完畢.所以最好的辦法還是綁定一個回調,當函數執行完畢時,自動觸發回調.
需要注意的是,在調用 submit 方法之後,提交到線程池的函數就已經開始執行了.而不管函數有沒有執行完畢,我們都可以給對應的 future 綁定回調.
如果函數完成之前添加回調,那麼會在函數完成後觸發回調.如果函數完成之後添加回調,由於函數已經完成,代表此時的 future 已經有值了,或者說已經 set_result 了,那麼會立即觸發回調.
future.set_result 到底干了什麼事情
當函數執行完畢之後,會執行 set_result,那麼這個方法到底干了什麼事情呢?
我們看到 future 有兩個被保護的屬性,分別是 _result 和 _state.顯然 _result 用於保存函數的返回值,而 future.result() 本質上也是返回 _result 屬性的值.而 _state 屬性則用於表示函數的執行狀態,初始為 PENDING,執行中為 RUNING,執行完畢時被設置為 FINISHED.
調用 future.result() 的時候,會判斷 _state 的屬性,如果還在執行中就一直等待.當 _state 為 FINISHED 的時候,就返回 _result 屬性的值.
提交多個函數
我們上面每次只提交了一個函數,但其實可以提交任意多個,我們來看一下:
如果是多個函數,要如何拿到返回值呢?很簡單,遍歷 futures 即可.
這裡面有一些值得說一說的地方,首先 futures 裡面有 5 個 future,記做 future1, future2, future3, future4, future5.
當使用 for 循環遍歷的時候,實際上會依次遍歷這 5 個 future,所以返回值的順序就是我們添加的函數的順序.由於 future1 對應的函數休眠了 5s,那麼必須等到 5s 後,future1 裡面才會有值.
但這五個函數是並發執行的,future2, future3, future4 由於只休眠了 2s, 4s, 3s,所以肯定會先執行完畢,然後執行 set_result,將返回值設置到對應的 future 裡.
但 Python 的 for 循環不可能在第一次迭代還沒有結束,就去執行第二次迭代.因為 futures 裡面的幾個 future 的順序已經一開始就被定好了,只有當第一個 future.result() 執行完成之後,才會執行第二個 future.result(),以及第三個、第四個.
因此即便後面的函數已經執行完畢,但由於 for 循環的順序,也只能等著,直到前面的 future.result() 執行完畢.所以當第一個 future.result() 結束時,後面三個 future.result() 會立刻輸出,因為它們內部的函數已經執行結束了.
而最後一個 future,由於內部函數 sleep 了 6 秒,因此要再等待 1 秒,才會打印 future.result().
【學習交流群853991558】
【網盤免費資料包,大家感興趣的可以看一下】:
PythonSo hot data package【華清遠見發放資料包】http://makerschool.mikecrm.com/6cvAGKm
【Below to share some free tutorials,大家感興趣的可以看一下】:
python入門
Python的封裝
Python網絡編程
python高級用法
Python爬蟲開發
python 人工智能-神經網絡
django項目案例精講
Artificial intelligence interview question answer
雲計算 大數據和人工智能
快速學習爬蟲基礎