談異步線程池的設計
異步線程池的作用就是統一進行線程調度,利用有限的線程處理系統中所有的異步任務。其實一個系統中的異步線程數量跟
CPU的核數是有一定的關係,線程太多,容易浪費資源,而且各個線程之間的CPU調度,也不見得使性能提高,反而會對性能有一定的影響。
但是,線程多,不容易照成死鎖。爲什麼呢?
很多情況,我們在設計異步線程池的時候,很少去考慮異步任務的依賴的關係,假如一個異步任務在執行過程中會再創建一個異步任務,
並且等待這個異步任務的返回,才最終返回。因此異步任務之間也存在依賴關係。理想的情況下,異步任務之間如果不存在依賴關係,那麼
阻塞是作用在調用者線程,這樣是不會照成死鎖的。因爲即使線程數量有限,總會把任務執行完畢。但是如果異步任務如下圖的依賴關係,
在有限的線程池規律條件下,是會死鎖的。
圖一:異步任務的依賴關係
從圖一可以看出,假如線程池規模是100,那麼如果有100一個併發產生的異步任務A、B、C任務,那麼A、B、C產生的
子任務D、E、F這些子任務是無法得到線程池去執行的。
那麼如何解決呢?解決死鎖的方法,就是破壞產生死鎖的一個條件。
按照理想的設計,如果異步任務隊列執行的異步任務不存在同步等待,那麼不會出現這樣的問題,因爲每個任務執行了,
如果還未執行完,可以被調度到等待隊列,或者只是執行一些無法等待的操作。但是這種設計在除了操作系統外,
好像很難實現,因爲不管無論怎麼設計,總會在某一個線程去等待,取決業務,有時可以執行某個動作後,
把該任務轉移到其他線程池,比如操作系統線程池。例如文件異步讀寫任務,就是把任務提交到操作系統線程去執行,
用戶線程池不會堵塞。
好像談多了一點,還是聚集一下在有依賴、等待問題的異步任務設計問題的解決上,前面提到解決這樣的問題就是破壞死鎖的條件,
那麼如何破壞呢?
解決方法如下:
1)比較粗魯的方法,各個異步任務都有自己的線程池,但是這樣做存在線程利用率低的問題。
2)根據異步任務的依賴順序,來確定任務的優先級,比如最底層依賴是優先級最高的,從依賴層來設計線程池,屬於同層的任務有共同的線程池來完成。
同層的異步任務沒有依賴關係,其產生的依賴任務由其他線程池完成,因此不會產生死鎖,只是處理時間的問題。
不過方法二相對方法一沒有什麼改進之處,只是線程利用率在某種情況下相對高點。
3)在統一的線程池內處理,只是對不同優先級的任務做流控。比如在任何時刻不允許某幾個優先級的異步任務耗盡線程池中的線程,總得預留一些線程處理其他異步任務, 在下發異步任務的地方進行阻塞。
方法三,可以編寫相對通用的異步線程池。
4)如果有可能,儘量採用不阻塞的任務,比如採用利用操作系統的異步機制,將任務下發到操作系統的隊列,等待其回調。比如文件系統的AIO、epoll等。
或者從業務角度劃分業務線程,比如IO線程、SOCKET線程、數據庫線程這些解耦合的異步線程。異步線程處理的任務之間是不依賴的,
把阻塞作用在使用者身上。