對執行緒池的理解
自從看了老趙關於執行緒池的實驗以後,我就想學著做一個類似的實驗,驗證自己的理解,現在終於做好了,請大家指正。
一般情況下我們都使用Thread類建立執行緒,因為通過Thread物件可以對執行緒進行靈活的控制。但建立執行緒和銷燬執行緒代價不菲,過多的執行緒會消耗掉大量的記憶體和CPU資源,假如某段時間內突然爆發了100個短小的執行緒,建立和銷燬這些執行緒就會消耗很多時間,可能比執行緒本身執行的時間還長。為了改善這種狀況,.NET提供了一種稱之為執行緒池(Thread Pool)的技術。執行緒池提供若干個固定執行緒輪流為大量的任務服務,比如用10個執行緒輪流執行100個任務,當一個執行緒完成任務時,並不馬上銷燬,而是接手另一個任務,從而減少建立和銷燬執行緒的消耗。
一、SetMaxThreads()的作用
MSDN裡這樣解釋:“檢索可以同時處於活動狀態的執行緒池請求的數目。所有大於此數目的請求將保持排隊狀態,直到執行緒池執行緒變為可用。”。這句話可真夠拗口,按我的理解,就是設定執行緒池中所能容納的最大執行緒數目,當任務數大於這個最大值時,多餘的任務將線上程池外排隊等候。
下面我們做個試驗來驗證一下,向一個執行緒池中排入50個執行緒,觀察活動執行緒數(正在執行任務的執行緒,不包括空閒執行緒)的變化情況。
using System; using System.Collections.Generic; using System.Text; usingSystem.Threading; using System.Diagnostics; namespace ThreadPoolTest { class Program { public static void ThreadPoolTest() { ThreadPool.SetMaxThreads(20, 20); //執行緒數目上限設定為20 ThreadPool.SetMinThreads(10, 10); //執行緒數目下限設定為10 Console.WriteLine("活動執行緒數\t"+ "執行緒名稱\t" + "狀態"); //向執行緒池中新增50個工作執行緒 for (int i = 1; i <= 50; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(WorkFunction), i); } } private static object Lock = new object(); //工作函式 public static void WorkFunction(object n) { lock (Lock) { Console.WriteLine(GetNumberOfRuningThreads() + "\t\tTask" + n + "\t\tstarted"); } //Do something Thread.Sleep(18681); lock (Lock) { Console.WriteLine(GetNumberOfRuningThreads() + "\t\tTask" + n + "\t\tfinished"); } } //獲取正在執行任務的執行緒數 public static int GetNumberOfRuningThreads() { int max1, max2; ThreadPool.GetMaxThreads(out max1, out max2); int available1, available2; ThreadPool.GetAvailableThreads(out available1, out available2); return (max1 - available1); } static void Main(string[] args) { ThreadPoolTest(); Console.ReadKey(); //按下任意鍵結束程式 } } }
執行結果如下:
活動執行緒數 執行緒名稱 狀態
1 Task1 started
2 Task2 started
3 Task3 started
4 Task4 started
5 Task5 started
6 Task6 started
7 Task7 started
8 Task8 started
9 Task9 started
10 Task10 started
11 Task11 started
12 Task12 started
13 Task13 started
14 Task14 started
15 Task15 started
16 Task16 started
17 Task17 started
18 Task18 started
19 Task19 started
20 Task20 started
20 Task1 finished
20 Task21 started
20 Task3 finished
20 Task22 started
20 Task2 finished
20 Task23 started
20 Task4 finished
20 Task24 started
20 Task6 finished
20 Task25 started
20 Task8 finished
20 Task26 started
20 Task5 finished
20 Task27 started
20 Task9 finished
20 Task28 started
20 Task7 finished
20 Task29 started
20 Task10 finished
20 Task30 started
20 Task11 finished
20 Task31 started
20 Task12 finished
20 Task32 started
20 Task13 finished
20 Task33 started
20 Task14 finished
20 Task34 started
20 Task15 finished
20 Task35 started
20 Task16 finished
20 Task36 started
20 Task17 finished
20 Task37 started
20 Task18 finished
20 Task38 started
20 Task19 finished
20 Task39 started
20 Task20 finished
20 Task40 started
20 Task21 finished
20 Task41 started
20 Task22 finished
20 Task42 started
20 Task23 finished
20 Task43 started
20 Task24 finished
20 Task44 started
20 Task25 finished
20 Task45 started
20 Task26 finished
20 Task46 started
20 Task28 finished
20 Task47 started
20 Task29 finished
20 Task48 started
20 Task27 finished
20 Task49 started
20 Task30 finished
20 Task50 started
20 Task31 finished
19 Task32 finished
18 Task33 finished
17 Task34 finished
16 Task35 finished
15 Task36 finished
14 Task37 finished
13 Task38 finished
12 Task39 finished
11 Task40 finished
10 Task41 finished
9 Task42 finished
8 Task43 finished
7 Task44 finished
6 Task45 finished
5 Task46 finished
4 Task47 finished
3 Task48 finished
2 Task49 finished
1 Task50 finished
(沒想到資料這麼完美,運氣挺好的)
從上面的結果可以看出,執行緒的隨著任務的增加,活動執行緒的數目也逐步增加,直到達到上限值20為止,之後,活動執行緒的數目保持不變,多餘的任務線上程池外排隊,當有執行緒完成任務時,就從排隊等候的執行緒中取一個來執行,因此雖然任務數在變化,但執行緒數目保持不變。隨著任務的不斷完成,某一時刻,任務數少於20個,此時活動執行緒也開始逐步減少。
二、SetMinThreads()的作用
MSDN中這樣解釋:“檢索執行緒池在新請求預測中維護的空閒執行緒數。”
這句話我也挺“專業”的,不太好懂。我的理解是:設定執行緒池中執行緒數目的下限,當任務小於下限時,不足的用空執行緒補足。
如何才能驗證這一點呢?我首先想到的是察看匯流排程數目的變化情況(包括執行任務的和空閒的執行緒),如果匯流排程的最小值是MinThreads,就說明我們的猜測是正確的。但我找了半天,ThreadPool類並沒有提供這樣的介面,所以我們需要另想辦法。
我的辦法是觀察執行緒的建立的時間。按照設想,執行緒池中本來就有若干空執行緒,所以一開始執行緒開始的時間非常快。當空執行緒用完時,也就是當任務數量會超出執行緒數量時,執行緒池並不會立即建立新執行緒,而是等待大約500毫秒左右,這麼做的目的是看看在這段時間內是否有其他執行緒完成任務來接手這個請求,這樣就可以避免因建立新執行緒而造成的消耗。所以,後面的執行緒建立速度應該比較慢。
總之一開始,建立執行緒的速度應該是很快的,當執行緒數超過下限值時,建立速度就會慢下來。
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Diagnostics; namespace ThreadPoolTest { class Program { public static Stopwatch watch = new Stopwatch(); public static void ThreadPoolTest() { watch.Start(); ThreadPool.SetMaxThreads(20, 20); //執行緒數目上限設定為20 ThreadPool.SetMinThreads(10, 10); //執行緒數目下限設定為10 Console.WriteLine("時間\t\t活動執行緒數\t" + "執行緒名稱\t" + "狀態"); //向執行緒池中新增50個工作執行緒 for (int i = 1; i <= 50; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(WorkFunction), i); } } private static object Lock = new object(); //工作函式 public static void WorkFunction(object n) { lock (Lock) { Console.WriteLine(watch.Elapsed + "\t" + GetNumberOfRuningThreads() + "\t\tTask" + n + "\t\tstarted"); } //Do something Thread.Sleep(38681); lock (Lock) { Console.WriteLine(watch.Elapsed + "\t" + GetNumberOfRuningThreads() + "\t\tTask" + n + "\t\tfinished"); } } //獲取正在執行任務的執行緒數 public static int GetNumberOfRuningThreads() { int max1, max2; ThreadPool.GetMaxThreads(out max1, out max2); int available1, available2; ThreadPool.GetAvailableThreads(out available1, out available2); return (max1 - available1); } static void Main(string[] args) { ThreadPoolTest(); Console.ReadKey(); //按下任意鍵結束程式 } } }
執行結果如下:
時間 活動執行緒數 執行緒名稱 狀態
00:00:00.0107238 1 Task1 started
00:00:00.0140175 3 Task3 started
00:00:00.0151379 7 Task7 started
00:00:00.0160629 8 Task2 started
00:00:00.0165277 8 Task8 started
00:00:00.0167671 9 Task5 started
00:00:00.0169506 10 Task9 started
00:00:00.0170967 10 Task4 started
00:00:00.0172783 10 Task10 started
00:00:00.0174646 10 Task6 started
00:00:01.0162788 11 Task11 started
00:00:02.0156464 12 Task12 started
00:00:03.0152283 13 Task13 started
00:00:04.0146323 14 Task14 started
00:00:05.0141607 15 Task15 started
00:00:06.0136234 16 Task16 started
00:00:07.0640600 17 Task17 started
00:00:08.0624471 18 Task18 started
00:00:09.0621401 19 Task19 started
00:00:10.0619472 20 Task20 started
00:00:38.7566261 20 Task1 finished
00:00:38.7576175 20 Task3 finished
00:00:38.7586037 20 Task22 started
00:00:38.7589606 20 Task10 finished
00:00:38.7609881 20 Task23 started
00:00:38.7644790 20 Task4 finished
00:00:38.7653911 20 Task24 started
00:00:38.7661620 20 Task9 finished
00:00:38.7667461 20 Task25 started
00:00:38.7676772 20 Task21 started
00:00:38.7683277 20 Task8 finished
00:00:38.7689285 20 Task26 started
00:00:38.7693858 20 Task2 finished
00:00:38.7700532 20 Task6 finished
00:00:38.7706129 20 Task28 started
00:00:38.7711820 20 Task5 finished
00:00:38.7716580 20 Task29 started
00:00:38.7722943 20 Task27 started
00:00:38.7728069 20 Task7 finished
00:00:38.7733115 20 Task30 started
00:00:39.7583735 20 Task11 finished
00:00:39.7591793 20 Task31 started
00:00:40.7578469 20 Task12 finished
00:00:40.7593474 20 Task32 started
00:00:41.7574471 20 Task13 finished
00:00:41.7582969 20 Task33 started
00:00:42.7569026 20 Task14 finished
00:00:42.7576400 20 Task34 started
00:00:43.7563349 20 Task15 finished
00:00:43.7570931 20 Task35 started
00:00:44.7559455 20 Task16 finished
00:00:44.7567992 20 Task36 started
00:00:45.7593267 20 Task17 finished
00:00:45.7599964 20 Task37 started
00:00:46.7568787 20 Task18 finished
00:00:46.7575400 20 Task38 started
00:00:47.7562341 20 Task19 finished
00:00:47.7569503 20 Task39 started
00:00:48.7568063 20 Task20 finished
00:00:48.7574898 20 Task40 started
00:01:17.4420354 20 Task22 finished
00:01:17.4438299 20 Task41 started
00:01:17.4464655 20 Task23 finished
00:01:17.4478110 20 Task42 started
00:01:17.4488933 20 Task24 finished
00:01:17.4496002 20 Task43 started
00:01:17.4508097 20 Task21 finished
00:01:17.4513800 20 Task44 started
00:01:17.4520468 20 Task25 finished
00:01:17.4526202 20 Task45 started
00:01:17.4532567 20 Task28 finished
00:01:17.4537159 20 Task46 started
00:01:17.4546202 20 Task26 finished
00:01:17.4552586 20 Task47 started
00:01:17.4558928 20 Task27 finished
00:01:17.4564289 20 Task48 started
00:01:17.4571296 20 Task30 finished
00:01:17.4577266 20 Task49 started
00:01:17.4582889 20 Task29 finished
00:01:17.4592571 20 Task50 started
00:01:18.4418865 20 Task31 finished
00:01:19.4424421 19 Task32 finished
00:01:20.4399553 18 Task33 finished
00:01:21.4405072 17 Task34 finished
00:01:22.4400143 16 Task35 finished
00:01:23.4394151 15 Task36 finished
00:01:24.4429568 14 Task37 finished
00:01:25.4404414 13 Task38 finished
00:01:26.4399282 12 Task39 finished
00:01:27.4404093 11 Task40 finished
00:01:56.1459537 10 Task41 finished
00:01:56.1485649 9 Task42 finished
00:01:56.1505286 8 Task43 finished
00:01:56.1514803 7 Task44 finished
00:01:56.1534702 6 Task45 finished
00:01:56.1544100 5 Task46 finished
00:01:56.1552989 4 Task47 finished
00:01:56.1574599 3 Task48 finished
00:01:56.1586138 2 Task49 finished
00:01:56.1602353 1 Task50 finished
(注意,由於執行緒輪換的不確定性,每次結果都會稍有不同)
從結果可以看出,前十個執行緒很短時間內開始,從第十一個執行緒開始則要經過較長一段時間才會建立(大約一秒)。從第二十個執行緒開始,要等前面的任務完成,新任務才能開始(這裡等了一段較長時間),完成一個,就開始一個,執行緒總數保持不變。直到任務數小於執行緒上限,活動執行緒數才開始逐漸減少。
總之,執行緒的執行過程可以描述如下(為了敘述方便,我們假設下限為10,上限為20):
1.當執行緒池被建立後,裡面就會建立10個空執行緒(和下限值相同)。
2.當我們向執行緒池中排入一個任務後,就會有一個空執行緒接手該任務,然後執行起來。隨著我們不斷向執行緒池中排入任務,執行緒池中的空執行緒逐一接手並執行任務。
3.隨著任務的不斷增加,在某一時刻任務數量會超出下限,這時執行緒的數量就不夠用了,但執行緒池並不會立即建立新執行緒,而是等待大約500毫秒左右,這麼做的目的是看看在這段時間內是否有其他執行緒完成任務來接手這個請求,這樣就可以避免因建立新執行緒而造成的消耗。如果這段時間內沒有執行緒完成任務,就建立一個新執行緒去執行新任務。
4.在任務數量超過下限後,隨著新任務的不斷排入,執行緒池中執行緒數量持續增加,直至執行緒數量達到上限值為止。
5.當執行緒數量達到上限時,繼續增加任務,執行緒數量將不再增加。比如你向執行緒池中排入50個任務,則只有20個進入執行緒池(和上限相同),另外30個線上程池外排隊等待。當執行緒池中的某個執行緒完成任務後,並不會立即終止,而是從等待佇列中選擇一個任務繼續執行,這樣就減少了因建立和銷燬執行緒而消耗的時間。
6.隨著任務逐步完成,執行緒池外部等候的任務被逐步調入執行緒池,任務的數量逐步減少,但執行緒的數量保持恆定,始終為20(和上限值相同)。
7.隨著任務被逐步完成,總有某一時刻,任務數量會小於上限值,這時執行緒池內多餘的執行緒會在空閒2分鐘後被釋放並回收相關資源。執行緒數目逐步減少,直到達到下限值為止。
8.當任務數量減小到下限值之下時,執行緒池中的執行緒數目保持不變(始終和下限值相同),其中一部分在執行任務,另一部分處於空執行狀態。
9.當所有任務都完成後,執行緒池恢復初始狀態,執行10個空執行緒。
由上面的論述可以看出執行緒池提高效率的關鍵是一個執行緒完成任務後可以繼續為其他任務服務,這樣就可以使用有限的幾個固定執行緒輪流為大量的任務服務,從而減少了因頻繁建立和銷燬執行緒所造成的消耗。
下面是修改Sleep()時間後再次執行的結果。
時間 活動執行緒數 執行緒名稱 狀態
00:00:00.0189359 9 Task2 started
00:00:00.0201952 9 Task1 started
00:00:00.0208337 9 Task9 started
00:00:00.0212512 9 Task6 started
00:00:00.0217229 9 Task8 started
00:00:00.0223773 9 Task7 started
00:00:00.0227899 9 Task4 started
00:00:00.0231066 9 Task3 started
00:00:00.0235849 9 Task5 started
00:00:01.0163201 10 Task10 started
00:00:02.0157285 11 Task11 started
00:00:03.0153552 12 Task12 started
00:00:04.0147554 13 Task13 started
00:00:05.0142889 14 Task14 started
00:00:06.0208071 15 Task15 started
00:00:06.7049884 15 Task2 finished
00:00:06.7070401 15 Task16 started
00:00:06.7074667 15 Task7 finished
00:00:06.7078560 15 Task17 started
00:00:06.7082879 15 Task3 finished
00:00:06.7086411 15 Task18 started
00:00:06.7092260 15 Task5 finished
00:00:06.7096711 15 Task19 started
00:00:06.7101447 15 Task6 finished
00:00:06.7111514 15 Task20 started
00:00:06.7146020 15 Task8 finished
00:00:06.7154948 15 Task21 started
00:00:06.7176304 15 Task9 finished
00:00:06.7193209 15 Task22 started
00:00:06.7201892 15 Task1 finished
00:00:06.7208777 15 Task23 started
00:00:06.7216695 15 Task4 finished
00:00:06.7230875 15 Task24 started
00:00:07.5200857 16 Task25 started
00:00:07.7023973 16 Task10 finished
00:00:07.7036477 16 Task26 started
00:00:08.5195702 17 Task27 started
00:00:08.7011903 17 Task11 finished
00:00:08.7025396 17 Task28 started
00:00:09.5187647 18 Task29 started
00:00:09.7008700 18 Task12 finished
00:00:09.7019751 18 Task30 started
00:00:10.5246393 19 Task31 started
00:00:10.7015599 19 Task13 finished
00:00:10.7025033 19 Task32 started
00:00:11.5184213 20 Task33 started
00:00:11.7000063 20 Task14 finished
00:00:11.7006650 20 Task34 started
00:00:12.6995112 20 Task15 finished
00:00:12.7003309 20 Task35 started
00:00:13.3848916 20 Task16 finished
00:00:13.3854166 20 Task36 started
00:00:13.3861911 20 Task17 finished
00:00:13.3865749 20 Task37 started
00:00:13.3875311 20 Task18 finished
00:00:13.3881098 20 Task38 started
00:00:13.3887310 20 Task19 finished
00:00:13.3893513 20 Task39 started
00:00:13.3920574 20 Task20 finished
00:00:13.3925907 20 Task40 started
00:00:13.3949921 20 Task21 finished
00:00:13.3956187 20 Task41 started
00:00:13.3980055 20 Task22 finished
00:00:13.3985151 20 Task42 started
00:00:13.3998460 20 Task23 finished
00:00:13.4002990 20 Task43 started
00:00:13.4022154 20 Task24 finished
00:00:13.4027421 20 Task44 started
00:00:14.1998829 20 Task25 finished
00:00:14.2003381 20 Task45 started
00:00:14.3827441 20 Task26 finished
00:00:14.3833120 20 Task46 started
00:00:15.1987473 20 Task27 finished
00:00:15.1993380 20 Task47 started
00:00:15.3824547 20 Task28 finished
00:00:15.3830691 20 Task48 started
00:00:16.1973072 20 Task29 finished
00:00:16.1980986 20 Task49 started
00:00:16.3807865 20 Task30 finished
00:00:16.3814246 20 Task50 started
00:00:17.2490713 20 Task31 finished
00:00:17.4271196 19 Task32 finished
00:00:18.2423627 18 Task33 finished
00:00:18.4271299 17 Task34 finished
00:00:19.4241792 16 Task35 finished
00:00:20.1084177 15 Task36 finished
00:00:20.1103091 14 Task37 finished
00:00:20.1113160 13 Task38 finished
00:00:20.1123464 12 Task39 finished
00:00:20.1162134 11 Task40 finished
00:00:20.1190785 10 Task41 finished
00:00:20.1220864 9 Task42 finished
00:00:20.1229967 8 Task43 finished
00:00:20.1259507 7 Task44 finished
00:00:20.9331819 6 Task45 finished
00:00:21.1069320 5 Task46 finished
00:00:21.9229240 4 Task47 finished
00:00:22.1070568 3 Task48 finished
00:00:22.9214465 2 Task49 finished
00:00:23.1049741 1 Task50 finished