.net中ThreadPool與Task的認識總結

https://www.cnblogs.com/vveiliang/p/7943003.html

線程池和Task是多線程編程中兩個經常使用的技術,大家在熟悉不過了。他們有什麼關聯關係?Task又是怎麼工作的呢?估計很多時候會犯糊塗。通過翻閱資料,終於弄明白了,與大家分享一下。

 

工作線程與I/O線程
     在ThreadPool中有這樣一個方法:

此方法中有兩個參數: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操作會進入設備自己的處理隊列中,該隊列由這個設備的驅動程序維護。

    如果此時是同步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的模式出隊執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章