Task的運行原理和工作竊取(work stealing)


    

在net4.0以前,當調用ThreadPool.QueueUserWorkItem方法往線程池中插入作業時,會把作業內容(其實就是一個委托)放到線程池中的一個全局隊列中,然后線程池中的線程按照先進先出的方式取出作業,並處理。

如下圖中的方式,主程序創建了Item到Queue中,然后分配到了各個工作線程中。

poolinaction    
但 是在.net 4.0以后,線程池做了一些改進,比如增加了TPL(Task Parallel Library),TPL使用到了.net 4.0中新增加的一些特性。這些特性只能通過TPL運用,不能直接通過ThreadPool類運用 。TPL中的Task並不是線程,Task的執行是需要依靠線程池中的線程來完成的。

創建和啟動一個Task類似調用 ThreadPool.QueueUserWorkItem,但不同的是線程池中的每一個線程都有一個本地隊列。線程池通過一個任務調度器來分配任務,當 主程序創建了一個Task后,由於創建這個Task的線程不是線程池中的線程,則任務調度器會把該Task放入全局隊列中。

如果這個Task是由線程池中的線程創建,並且未設置TaskCreationOptions.PreferFairness標記(默認情況下未設置),則任務調度器會把該Task放入到該線程的本地隊列中。如果設置了TaskCreationOptions.PreferFairness標記,則放入全局隊列。

如下面的演示圖,Task1和Task2都是主程序創建的,因此都是放在全局隊列中,當工作者線程處理Task2時,創建了一個Task3,此時Task3被放入本地隊列

llocalqueues

為什么要設計本地隊列?這樣做的優勢是充分利用並行。隨着越來越多線程競爭工作項,所有的線程訪問單一的隊列並不是最優的,並且也不安全。所以,將任務放入本地隊列,並且由同一個線程處理,這就避免了競爭。  
本地隊列中的Task,線程會按照LIFO的方式去處理。這是因為在大多數場景下,最后創建的Task可能仍然在cache中,處理它能夠提供緩存命中率。顯然這意味放棄部分公平性而保證性能。如下面的演示圖,

工作者線程1創建了Task2,Task2創建了Task3,Task4,Task5,但最先處理的還是Task5。


線程竊取work stealing    
當 A線程開始執行的時候,優先總是處理本地隊列中的任務,當它發現本地隊列已經空了,那么它會去全局隊列中獲取Task,當全局隊列中也是空的,那么就會發 生工作竊取(work stealing)。任務調度器會把該線程池中額外的任務分配給A線程處理,其效果就好比該線程會才從其他線程的隊列中“竊取”一個Task來執行。這樣 的目的是提高了cpu的使用效率。

readpoollifo

這種策略是任務調度器的默認策略,通常是不需要改變的。如果需要改變,需要在創建任務時,設置任務的TaskCreationOptions.PreferFairness。

----------------------

參考資料

http://www.danielmoth.com/Blog/New-And-Improved-CLR-4-Thread-Pool-Engine.aspx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM