線程池和Task是多線程編程中兩個常用的技術,你們在熟悉不過了。他們有什麼關聯關係?Task又是怎麼工做的呢?估計不少時候會犯糊塗。經過翻閱資料,終於弄明白了,與你們分享一下。編程
工做線程與I/O線程
在ThreadPool中有這樣一個方法:windows
public static bool SetMaxThreads(int workerThreads, int completionPortThreads);多線程
此方法中有兩個參數:workerThreads和completionPortThreads。這兩個參數引伸出了兩個概念:輔助線程(也叫工做線程)和異步 I/O 線程。這兩個線程有什麼區別麼?經過查閱資料,咱們能夠了解到,工做線程其實就是咱們編碼主動向ThreadPool中建立的線程。而I/O線程是線程池中預先保留出來的部分線程,這部分線程的做用是爲了分發從IOCP(I/O completion port) 中的回調。異步
那麼什麼是IOCP回調呢?函數
在CLR內部,系統維護了一個IOCP(I/O completion port),它提供了處理多個異步I/O請求的線程模型。咱們能夠把IOCP看作是一個消息隊列。當一個進程建立了一個IOCP,即建立了一個隊列。當異步I/O請 求完成時,設備驅動程序就會生成一個I/O完成包,將它按照FIFO方式排隊列入該完成端口。以後,會由I/O線程提取完成I/O請求包,並調用以前的委託。注意:異步調用服務時,回調函數都是運行於CLR線程池的I/O線程當中。性能
I/O線程是由CLR調用的,一般狀況下,咱們不會直接用到它 。可是線程池中區分它們的目的是爲了不線程都去處理I/O回調而被耗盡,從而引起死鎖。在編程時,開發人員須要關注的是確保I/O線程返回到線程池,I/O回調代碼應該作儘可能小的工做,並儘快返回到線程池,不然I/O線程會很快消耗光。若是回調代碼中的工做不少的話,應該考慮把工做拆分到一個工做者線程中去。不然,I/O線程被耗盡,大量工做線程空閒,可能致使死鎖。編碼
再補充一下,當執行I/O操做的時候,不管是同步I/O操做仍是異步I/O操做,都會調用的Windows的API方法,好比,當讀取文件時,調用ReadFile函數。該方法會將你的當前線程從用戶態轉變成內核態,會生成一個I/O請求包,並初始化這個請求包。ReadFile會向內核傳遞,根據這個請求包,windows內核知道須要將這個I/O操做發送給哪一個硬件設備。這些I/O操做會進入設備本身的處理隊列中,該隊列由這個設備的驅動程序維護。spa
若是此時是同步I/O操做,那麼在硬件設備操做I/O的時候,發出I/O請求的線程因爲無事可作被windows變成睡眠狀態,當硬件設備完成操做後,再喚醒這個線程。這種方式很是直接,可是性能不高,若是請求數不少,那麼休眠的線程數也不少,浪費了大量資源。線程
若是是異步I/O操做(.Net中,異步的I/O操做大部分爲BeginXXX的形式 ),該方法在Windows把I/O請求包發送到設備的處理隊列後就返回。同時,在調用異步I/O操做時,即調用BeginXXX方法的時候,須要傳入一個委託,該委託方法會隨着I/O請求包一路傳遞到設備的驅動程序。在設備處理完I/O請求包後,將該委託再放到CLR線程池隊列。設計
總結來講,IOCP(I/O completion port)中有2個隊列,一個是先進先出的隊列,存放的是IO完成包,即已經完成的IO操做須要執行回調方法。還有一個隊列是線程隊列,IOCP會預分配一些線程在這個隊列中,這樣會比即時建立線程處理I/O請求速度更快。這個隊列是後進先出的,好處是下一個請求的到來可能仍是用以前的線程來處理,就不須要進行線程上下文切換,提升了性能。
這裏有一個IOCP的解釋,寫的很好。http://gamebabyrocksun.blog.163.com/blog/static/57153463201036104134250/
Task的運行原理分析
Task與ThreadPool什麼關係呢?簡單來講,Task是基於ThreadPool實現的,固然被標記爲LongRunning的Task(單首創建線程實現)除外。Task被建立後,經過TaskScheduler執行工做項的分配。TaskScheduler會把工做項存儲到兩類隊列中: 全局隊列與本地隊列。全局隊列被設計爲FIFO的隊列。本地隊列存儲在線程中,被設計爲LIFO.
當主程序建立了一個Task後,因爲建立這個Task的線程不是線程池中的線程,則TaskScheduler 會把該Task放入全局隊列中。
若是這個Task是由線程池中的線程建立,而且未設置TaskCreationOptions.PreferFairness標記(默認狀況下未設置),TaskScheduler 會把該Task放入到該線程的本地隊列中。若是設置了TaskCreationOptions.PreferFairness標記,則放入全局隊列。
官方的解釋是: 最高級任務(即不在其餘任務的上下文中建立的任務)與任何其餘工做項同樣放在全局隊列上。 可是,嵌套任務或子任務(在其餘任務的上下文中建立)的處理方式大不相同。 子任務或嵌套任務放置在特定於執行父任務的線程的本地隊列上。 父任務多是最高級任務,也多是其餘任務的子任務。
那麼任務放入到兩類隊列中後,是如何被執行的呢?
當線程池中的線程準備好執行更多工做時,首先查看本地隊列。 若是工做項在此處等待,直接經過LIFO的模式獲取執行。 若是沒有,則向全局隊列以FIFO的模式獲取工做項。若是全局隊列也沒有工做項,則查看其餘線程的本地隊列是否有可執行工做項,若是存在可執行工做項,則以FIFO的模式出隊執行。