其實說到上一篇,我們要說的task的知識也說的差不多了,這一篇我們開始站在理論上了解下“線程池”和“任務”之間的關系,不管是
說線程還是任務,我們都不可避免的要討論下線程池,然而在.net 4.0以后,線程池引擎考慮了未來的擴展性,已經充分利用多核微處理器
架構,只要在可能的情況下,我們應該盡量使用task,而不是線程池。
首先看一下task的結構
從圖中我們可以看出Task.Factory.StartNew()貌似等同於用ThreadPool.QueueUserWorkItem()創建,但是請注意,我是用TPL的形式
使用線程池,要知道task出現以后,一直標榜着以更少的工作量,更低的性能消耗來PK原始線程。
這里簡要的分析下CLR線程池,其實線程池中有一個叫做“全局隊列”的概念,每一次我們使用QueueUserWorkItem的使用都會產生一個
“工作項”,然后“工作項”進入“全局隊列”進行排隊,最后線程池中的的工作線程以FIFO的形式取出,效果圖類似如下:
這里要值得一提的是,在.net 4.0之后“全局隊列”采用了無鎖算法,相比以前版本鎖定“全局隊列”帶來的性能瓶頸有了很大的改觀。那么任務
委托的線程池不光有“全局隊列”,而且每一個工作線程都有”局部隊列“,效果圖如下
我們的第一反應肯定就是“局部隊列“有什么好處,可以考慮這樣的情況,當我們new一個task的時候“工作項”就會進去”全局隊列”,如果我們的
task執行的非常快,那么“全局隊列“就會FIFO的非常頻繁,那么有什么辦法緩解呢?當我們的task在嵌套的場景下,“局部隊列”就要產生效果了,
比如我們一個task里面有3個task,那么這3個task就會存在於“局部隊列”中。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var task = Task.Factory.StartNew(() => 6 { 7 var task1 = Task.Factory.StartNew(Run1); 8 var task2 = Task.Factory.StartNew(Run2); 9 var task3 = Task.Factory.StartNew(Run3); 10 11 Task.WaitAll(new Task[] { task1, task2, task3 }); 12 }); 13 14 Console.Read(); 15 } 16 17 public static void Run1() { Thread.Sleep(100000); } 18 19 public static void Run2() { Thread.Sleep(100000); } 20 21 public static void Run3() { Thread.Sleep(100000); } 22 }
從圖中可以看到,其實“局部隊列“起到了一個分流的作用,也叫做”任務內聯化“,”局部隊列“采用的是”LIFO"的形式,其實這樣的形式也是
為了提升性能之用,因為Run3送到“局部隊列”中時可能還存在CPU的高速緩存中,所以從“局部隊列”中取出來相對來說更快一點,最后的效
果就是Run3要理論上優先於Run2,Run1先執行。
現在我們再來考慮這樣一種情況,比如有兩個人,一個人干完了分配給自己的所有活,而另一個人卻還有很多的活,從人情上說,閑的人應
該接手點忙的人的活,同樣,對應圖中“線程2“跑完了“局部隊列”中的所有任務,並且同時發現”全局隊列“中已經沒有可以跑的”任務“了,然而
“線程1”里面還有Run1,Run2,Run3,那么此時“線程2”采用“FIFO”的形式竊取“線程1”里面的任務。
從上面種種情況我們看到,這些分流和負載都是普通ThreadPool.QueueUserWorkItem所不能辦到的,所以說在.net 4.0之后,我們
盡可能的使用TPL,拋棄ThreadPool。