2019年7月4日 星期四

何謂 C# 執行緒集區 Thread Pool,以及其運作方式探討

何謂 C# 執行緒集區 Thread Pool,以及其運作方式探討

在使用 C# 來開發應用程式的時候,若想要能夠做到多樣工作可以並行或平行方式的來運作,這個時候就需要建起多個執行緒來完成這樣的需求。通常,都不會建議直接使用 Thread 類別來建立起一個新的執行緒,透過該執行緒來執行所指定的委派方法,而是採用透過 執行緒集區 TreadPool 來取得一個背景執行緒,透過該背景執行緒來執行要併行/平行執行的委派方法;最明顯的例子就是 Task.Run 這個產生出一個非同步工作的方法,就是透過執行緒集區來取得一個執行緒,在這個執行緒內來執行這個非同步的工作。
不過,執行緒集區也不是萬能的,他也會有使用上的限制與瓶頸,執行緒集區設計的目的不是要降低延遲性,而是要提供整體系統運作效能,程式設計師不要專注在執行緒上的管理,就可以使用到執行緒帶來的好處;那麼,什麼是 執行緒集區 Thead Pool 呢?
這篇文章所使用到的程式碼,可以從 GitHub 取得
執行緒集區 Thead Pool 是一個類別,可以透過該類別指派一個委派方法,讓這個方法在集區內的任一個執行緒內來執行;當執行完成之後,就會把這個執行緒歸還到執行緒集區內。因此,執行緒集區 Thead Pool 可以視為一個類似計程車行的觀念,在這個裡面擁有許多的計程車,當你需要計程車的時候,就可以打電話給 執行緒集區 Thead Pool,這樣,執行緒集區就會派送一台計程車給你,待送你到想要的地方;當你抵達到目的地之後,該計程車就會回到車行,等候下一個使用者來叫車。
不過,執行緒集區內究竟要準備多少個執行緒數量,才能夠滿足多工設計上的需求?這就有點像是到底要準備多少台計程車,才能夠滿足客戶叫車的需求呢?說實在的,這沒有一定的標準,畢竟清晨的時候叫車的人可能會比較少,可是,上下班、下雨天的時候,就會有可能會有很多的人需要叫車;因此,當大家都要叫計程車的時候,可是當時的計程車都去載客人的時候,車行的電話就有可能一直在饗,就算接起電話,也只能和客人說對不起,因為,現在已經沒有車子可以派車了,畢竟一次準備太多的車子,若沒有人充分利用這些車子,也會造成成本的浪費。
那麼,在處理程序 Process 啟動執行的時候,執行緒集區究竟會產生多少預設的執行緒數量呢?答案是每台電腦所產生的數量都不進相同,原則上會產生出這台電腦上邏輯處理器相同數量的執行緒數量,例如,在我這台電腦上有八顆邏輯處理器,因此,執行緒集區一起動的時候,將會建立起八個執行緒在該集區內。
要怎麼看出這台電腦有多少邏輯處理器呢?請使用工作管理這個程式,切換到 效能 標籤頁次,在該頁次右下方,將會看到這台電腦上總共有 4 核心的處理器,因為具有 Hyper-Threading 的技術,所以,總共會擁有 8 顆邏輯處理。此時,可以使用 Thread 這個類別內提供的 ThreadPool.GetMinThreads(Int32, Int32) 方法,查詢出該執行緒集區內預設會建立起多少個數量的執行緒。
從這個 GetMinThreads 方法中需要傳入兩個 out int 型別的參數,分別會得到 [執行緒集區視需要建立的背景工作執行緒最小數目] 與 [執行緒集區視需要建立的非同步 I/O 執行緒最小數目] 也就是分別會建立起 CPU Bound 與 I/O 會使用到的許多執行緒到執行緒集區中。
還有,當執行緒集區內的執行緒數量不夠用的時候,執行緒集區究竟可以產生出最多多少的執行緒到執行緒集區內呢?這個時候,就可以使用 ThreadPool.GetMaxThreads(Int32, Int32) 方法來進行查詢,一旦呼叫這個 API,則會得到兩個整數數值,分別表示了 執行緒集區中的背景工作執行緒最大數目 與 執行緒集區中的非同步 I/O 執行緒最大數目;這裡的數量並不表示執行緒集區內現在擁有的執行緒數量,而是當執行緒集區內的執行緒數量不夠用的時候,執行緒集區額外產生的執行緒數量,最大的上限值為多少。
現在可以執行這個範例程式將會看到底下的輸出結果,這表示該執行緒集區內在應用程式一啟動之後,就會產生出 8 個背景工作執行緒,也就是底下列出的 WorkItem Thread 那一列,與 8 個非同步 I/O 會用到的執行緒,也就是底下列出的 IOPC Thread 那一列資訊。從這台電腦的執行結果,因為該電腦有 8 顆邏輯處理器,因此,將會預設建立起 8 個背景工作會使用到的執行緒與 8 個非同步 I/O 會使用到的執行緒,而這兩類的執行緒分別最大可以產生到 32767 個執行緒數量
執行緒集區的相關設定資訊
邏輯處理器數目 : 8
WorkItem Thread : (Busy:0, Free:32767, Min:8, Max:32767)
IOPC Thread : (Busy:0, Free:1000, Min:8, Max:1000)
在底下的範例程式碼中,將會宣告兩個整數,並且分別呼叫了 GetAvailableThreads , GetMaxThreads , GetMinThreads 這三個方法,其中第一個方法 GetAvailableThreads 呼叫之後,將會得到了該執行緒內還有多少執行緒的數量可以用來產生出新的執行緒之用,因此,把 GetMaxThreads 呼叫這個方法所得到的整數值 減去 呼叫 GetAvailableThreads 方法所得到的數值,就可以知道現在執行緒集區內,已經產生出多少個新的額外執行緒數量了。
C Sharp / C#
int workerThreads;
int completionPortThreads;

// 傳回之執行緒集區的現在還可以容許使用多少的執行緒數量大小
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
threadPoolInformation.AvailableWorkerThreads = workerThreads;
threadPoolInformation.AvailableCompletionPortThreads = completionPortThreads;

// 擷取可並行使用之執行緒集區的要求數目。 超過該數目的所有要求會繼續佇列,直到可以使用執行緒集區執行緒為止
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads);
threadPoolInformation.MaxWorkerThreads = workerThreads;
threadPoolInformation.MaxCompletionPortThreads = completionPortThreads;

// 在切換至管理執行緒建立和解構的演算法之前,擷取執行緒集區隨著提出新要求,視需要建立的執行緒最小數目。
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
threadPoolInformation.MinWorkerThreads = workerThreads;
threadPoolInformation.MinCompletionPortThreads = completionPortThreads;

// 如果目前電腦包含多個處理器群組,則這個屬性會傳回可供 Common Language Runtime (CLR) 使用的邏輯處理器數目
threadPoolInformation.ProcessorCount= System.Environment.ProcessorCount;
在這篇文章中,將僅會針對非 非同步 I/O 類型,也就是背景工作執行緒來進行各種不同的測試與評估,了解到當執行緒集區遇到這樣的情境之後,將會發生甚麼樣的問題,已經日後該如何針對這樣的問題來進行處理呢?

執行緒集區運作方式的測試方法說明

在這裡個範例測試程式,將會使用 testLoop 來設定同時會有多少的背景工作執行緒會同時產生出了,這裡使用迴圈來使用 ThreadPool.QueueUserWorkItem 方法來建立起這些大量的背景執行緒,每次建立起一個背景執行緒,將會暫時休息 10 ms 時間,為的是讓輸出結果會比較整齊與漂亮些;在這些背景執行緒內,將會先取得該執行緒的受管理的執行緒 ID,接著,將使用 ShowCurrentThreadUsage 方法來顯示現在執行緒集區內的執行緒數量相關資訊,接著將會休息 1000 * backgroundExecuteTimes ms 時間 (這裡將會設定每個執行緒將會耗用 8 秒種的時間來模擬執行長時間的工作),這裡是用來模擬一個長時間等候非同步或者是大量運算的執行過程,也就是說該執行緒因為正在執行或者正在等候結果,所以,該執行緒是無法歸還給執行緒集區的;最後當這些處理程序都完成之後,將會再度顯示出執行緒集區內的相關資訊與準備要結束該執行緒的時間點。
透過這些資訊將會用來觀察當有不同數量的執行緒同時產生的時候,執行緒集區將會如何針對這些不同的執行情況來提供各種不同的服務;不過,在此先要了解,執行緒集區存在的目的使要降地直接使用執行緒的麻煩與不用去管理這些執行緒,也就是說不是要降低系統運作低延遲性,而是要提升整體執行緒的使用效率。

當要產生出低於執行緒集區預設建立的執行緒數量

在這台測試電腦上,執行緒集區將會預設建立起 8 個執行緒在該集區內,因此,請修正這個 testLoop 變數值介於 1~8 之前,要求測試程式能夠同時產生出這些數量的執行緒;一旦開始執行測試之後,因為執行緒集區內還有足夠的執行緒可以使用,因此每次呼叫 ThreadPool.QueueUserWorkItem 方法之後,會先將需求存放到執行緒集區內的 Queue 佇列內,若執行緒集區內有剩餘可用的執行緒,就可以取得這些執行緒來執行所指定委派方法。在這裡將會進行最大的可用執行緒測試,也就是同時要求 8 個執行緒(不過,若你的電腦內的執行緒集區預設僅有 4 個可用執行緒,請記得要修正 testLoop 的值為 4),請確認 testLoop 的變數值為 8,在此將會得到底下的執行結果
從底下的執行結果可以看出,執行緒[1] ~ [8] 分別於 09:34:49.0166567 ~ 09:34:49.0928839 = 0.076 秒的時間內,就從執行緒集區內獲得執行緒,並且開始執行,而這 8 個執行緒也成功地於 09:34:57.0179431 ~ 09:34:57.0939583 結束執行,也就是都花費了 8 秒的時間結束執行,可以看出這樣的執行效能相當的優異。
執行緒集區的相關設定資訊
邏輯處理器數目 : 8
WorkItem Thread : (Busy:0, Free:32767, Min:8, Max:32767)
IOPC Thread : (Busy:0, Free:1000, Min:8, Max:1000)

準備產生出 8 個執行緒
請按下任一按鍵,進行執行緒集區的使用模擬
 執行緒[1]開始: ID=4, time=09:34:49.0166567
   WorkItem Thread : (Busy:1, Free:32766, Min:8, Max:32767)
執行緒[2]開始: ID=5, time=09:34:49.0269256
   WorkItem Thread : (Busy:3, Free:32764, Min:8, Max:32767)
執行緒[3]開始: ID=6, time=09:34:49.0379935
   WorkItem Thread : (Busy:4, Free:32763, Min:8, Max:32767)
執行緒[4]開始: ID=7, time=09:34:49.0489234
   WorkItem Thread : (Busy:5, Free:32762, Min:8, Max:32767)
執行緒[5]開始: ID=8, time=09:34:49.0599068
   WorkItem Thread : (Busy:6, Free:32761, Min:8, Max:32767)
執行緒[6]開始: ID=9, time=09:34:49.0708600
   WorkItem Thread : (Busy:7, Free:32760, Min:8, Max:32767)
執行緒[7]開始: ID=10, time=09:34:49.0819126
   WorkItem Thread : (Busy:8, Free:32759, Min:8, Max:32767)
執行緒[8]開始: ID=11, time=09:34:49.0928839
   WorkItem Thread : (Busy:8, Free:32759, Min:8, Max:32767)
執行緒[1]結束: ID=4, time=09:34:57.0179431
   WorkItem Thread : (Busy:8, Free:32759, Min:8, Max:32767)
執行緒[2]結束: ID=5, time=09:34:57.0279241
   WorkItem Thread : (Busy:7, Free:32760, Min:8, Max:32767)
執行緒[3]結束: ID=6, time=09:34:57.0389303
   WorkItem Thread : (Busy:6, Free:32761, Min:8, Max:32767)
執行緒[4]結束: ID=7, time=09:34:57.0499451
   WorkItem Thread : (Busy:5, Free:32762, Min:8, Max:32767)
執行緒[5]結束: ID=8, time=09:34:57.0609511
   WorkItem Thread : (Busy:4, Free:32763, Min:8, Max:32767)
執行緒[6]結束: ID=9, time=09:34:57.0719209
   WorkItem Thread : (Busy:3, Free:32764, Min:8, Max:32767)
執行緒[7]結束: ID=10, time=09:34:57.0829550
   WorkItem Thread : (Busy:2, Free:32765, Min:8, Max:32767)
執行緒[8]結束: ID=11, time=09:34:57.0939583
   WorkItem Thread : (Busy:1, Free:32766, Min:8, Max:32767)

當要產生出高於執行緒集區預設建立的執行緒數量

現在要來模擬當有高於執行緒集區預設建立的執行緒數量情境之下,會發生甚麼樣的情況,在這裡會將 testLoop 變數值設定為 12 也就是說,若同時要求 12 個執行緒,執行緒集區內最多僅能夠提供出 8 個可用執行緒可以立即使用,那麼,對於另外 4 個需要執行緒要求,執行緒集區該如何處理呢?
從底下的執行結果可以看到 執行緒[1]~執行緒[8] 幾乎在相同的時間點 (09:45:22) 獲得了執行緒來執行所指定的委派方法,而 執行緒[9]~執行緒[12] 則會分別於 09:45:23.7050644 , 09:45:24.7040167 , 09:45:25.7051190 , 09:45:26.7052975 的時間點才會取得所需要的執行緒;會有這樣的結果這是因為當執行緒集區發現到可用的執行緒不夠用的時候,而且執行緒集區的佇列 Queue 內還有需求在等候執行緒的時候,將會每個 0.5 ~ 1 秒鐘 (這樣的時間點,將會視當時電腦硬體的情況與所使用的 CLR 版本而定,並不會固定不變),持續的產生出新的執行緒,這是因為原先的 8 個執行緒持續霸佔著執行緒,而沒有歸還給執行緒集區,而在執行緒集區內又不斷有新的要求執行緒集區能夠配置出執行緒出來,因此,將會產生出新執行緒出來。
可是,你一定會很納悶的,同一個時間點,在執行緒集區佇列內有 4 個需求在等待執行緒要求,而執行緒集區卻是慢慢的產生出新的執行緒出來,這是故意設計這樣的機制,為的是要避免產生 執行緒醒來風暴 thread pool wakeup storms 的問題。而當額外產生出來的執行緒執行完畢之後,將會歸還給執行緒集區,而執行緒集區也會觀察,若在一定時間內,若這些新產生出來的執行緒沒有被使用的話,將會歸還給系統,而原先執行緒集區所建立的預設 8 個執行緒,不論多久,也不會歸還給作業系統,除非這個處理程序結束執行。
執行緒集區的相關設定資訊
邏輯處理器數目 : 8
WorkItem Thread : (Busy:0, Free:32767, Min:8, Max:32767)
IOPC Thread : (Busy:0, Free:1000, Min:8, Max:1000)

準備產生出 12 個執行緒
請按下任一按鍵,進行執行緒集區的使用模擬
 執行緒[1]開始: ID=4, time=09:45:22.7045868
   WorkItem Thread : (Busy:1, Free:32766, Min:8, Max:32767)
執行緒[2]開始: ID=5, time=09:45:22.7139011
   WorkItem Thread : (Busy:3, Free:32764, Min:8, Max:32767)
執行緒[3]開始: ID=6, time=09:45:22.7248548
   WorkItem Thread : (Busy:4, Free:32763, Min:8, Max:32767)
執行緒[4]開始: ID=7, time=09:45:22.7358487
   WorkItem Thread : (Busy:5, Free:32762, Min:8, Max:32767)
執行緒[5]開始: ID=8, time=09:45:22.7468800
   WorkItem Thread : (Busy:6, Free:32761, Min:8, Max:32767)
執行緒[6]開始: ID=9, time=09:45:22.7578943
   WorkItem Thread : (Busy:7, Free:32760, Min:8, Max:32767)
執行緒[7]開始: ID=10, time=09:45:22.7689058
   WorkItem Thread : (Busy:7, Free:32760, Min:8, Max:32767)
執行緒[8]開始: ID=11, time=09:45:22.7798759
   WorkItem Thread : (Busy:8, Free:32759, Min:8, Max:32767)
執行緒[9]開始: ID=12, time=09:45:23.7050644
   WorkItem Thread : (Busy:9, Free:32758, Min:8, Max:32767)
執行緒[10]開始: ID=13, time=09:45:24.7040167
   WorkItem Thread : (Busy:10, Free:32757, Min:8, Max:32767)
執行緒[11]開始: ID=14, time=09:45:25.7051190
   WorkItem Thread : (Busy:11, Free:32756, Min:8, Max:32767)
執行緒[12]開始: ID=15, time=09:45:26.7052975
   WorkItem Thread : (Busy:12, Free:32755, Min:8, Max:32767)
執行緒[1]結束: ID=4, time=09:45:30.7067440
   WorkItem Thread : (Busy:12, Free:32755, Min:8, Max:32767)
執行緒[2]結束: ID=5, time=09:45:30.7147327
   WorkItem Thread : (Busy:11, Free:32756, Min:8, Max:32767)
執行緒[3]結束: ID=6, time=09:45:30.7257740
   WorkItem Thread : (Busy:10, Free:32757, Min:8, Max:32767)
執行緒[4]結束: ID=7, time=09:45:30.7367508
   WorkItem Thread : (Busy:9, Free:32758, Min:8, Max:32767)
執行緒[5]結束: ID=8, time=09:45:30.7477686
   WorkItem Thread : (Busy:8, Free:32759, Min:8, Max:32767)
執行緒[6]結束: ID=9, time=09:45:30.7587406
   WorkItem Thread : (Busy:7, Free:32760, Min:8, Max:32767)
執行緒[7]結束: ID=10, time=09:45:30.7697294
   WorkItem Thread : (Busy:6, Free:32761, Min:8, Max:32767)
執行緒[8]結束: ID=11, time=09:45:30.7807757
   WorkItem Thread : (Busy:5, Free:32762, Min:8, Max:32767)
執行緒[9]結束: ID=12, time=09:45:31.7057277
   WorkItem Thread : (Busy:4, Free:32763, Min:8, Max:32767)
執行緒[10]結束: ID=13, time=09:45:32.7048023
   WorkItem Thread : (Busy:3, Free:32764, Min:8, Max:32767)
執行緒[11]結束: ID=14, time=09:45:33.7057667
   WorkItem Thread : (Busy:2, Free:32765, Min:8, Max:32767)
執行緒[12]結束: ID=15, time=09:45:34.7077598
   WorkItem Thread : (Busy:1, Free:32766, Min:8, Max:32767)

當要產生出高於執行緒集區最大允許的執行緒數量

在這裡將會模擬一個情況,若執行緒集區已經產生出執行緒集區所允許的最大可用執行緒數量,此時,會發生甚麼情況,在這台電腦上,執行緒集區最多的執行緒數量為 32767 個,每次執行 ThreadPool.QueueUserWorkItem 並且取得一個執行緒之後,這個數值將會減一,這樣的結果可以從上一個執行範例結果中看到,一開始該執行緒集區內可以產生最大執行緒數量為 32767,而當 12 個執行緒都產生出來的時候,可用執行緒數量將會降低到 32755 個。
為了要能夠模擬這樣的情境,要產生出 32767 以上的執行緒數量,似乎有點麻煩,因為,在這裡將會使用 ThreadPool.SetMaxThreads(10, 10); 這個方法,來設定執行緒集區僅能夠最多產生出 10 個執行緒,若接下來執行緒佇列內有新的執行緒產生需求,這個時候,很無奈的,只能夠無限制地等待,直到有執行緒執行完畢之後,歸還給執行緒集區,這樣,在執行緒集區佇列內等候者,才能夠再度獲得執行緒。會有這樣結果,這是因為執行緒集區所產生的實行續數量已經到達設定上限,將不會持續無限制的產生出新的執行緒出來,而這樣的情境將稱之為 執行緒飢餓 thread starvation。
所以,請將該測試範例程式 ThreadPool.SetMaxThreads(10, 10); 敘述解除註解,來看看執行結果
從底下的執行結果可以看到 執行緒[1]~執行緒[8] 幾乎在相同的時間點 (10:12:01.9361442 ~ 10:12:02.0111697) 獲得了執行緒來執行所指定的委派方法,而 執行緒[9]~執行緒[10] 則會分別於 10:12:02.0011936 , 10:12:03.9363546 的時間點才會取得所需要的執行緒,這樣的執行結果原則上符合上面測試結果,不過,對於 執行緒[11] 與 執行緒[12] 則會在 10:12:09.9479320 與 10:12:09.9587256 這兩個時間點才會獲得所需要的執行緒,比起前面的 10 個執行緒,幾乎慢了 8 秒鐘的時間還能夠取得所需要的執行緒。對於這樣的情境大家一定不陌上,那就是當有大量需求要連線到 IIS 主機上的時候,因為每個連線都會需要耗用到一個執行緒來處理,若該連線處理的時間要很久,將會導致這台 IIS 主機上的執行緒集區,因為不斷有新的要求加入到執行緒佇列內,而執行緒集區一直不斷的也為了滿足需求而建立起新的執行緒,最後的解果就是已經抵達執行緒集區所設定的最大上限數量,結果,就是後面來的要求將會還是停留在執行緒佇列內,痴痴等待執行緒集區內是否有執行緒已經執行完畢,才能夠獲得此執行緒才能夠執行相關程式碼。
執行緒集區的相關設定資訊
邏輯處理器數目 : 8
WorkItem Thread : (Busy:0, Free:10, Min:8, Max:10)
IOPC Thread : (Busy:0, Free:10, Min:8, Max:10)

準備產生出 12 個執行緒
請按下任一按鍵,進行執行緒集區的使用模擬
 執行緒[1]開始: ID=4, time=10:12:01.9361442
   WorkItem Thread : (Busy:1, Free:9, Min:8, Max:10)
執行緒[2]開始: ID=5, time=10:12:01.9461668
   WorkItem Thread : (Busy:3, Free:7, Min:8, Max:10)
執行緒[3]開始: ID=6, time=10:12:01.9571457
   WorkItem Thread : (Busy:4, Free:6, Min:8, Max:10)
執行緒[4]開始: ID=7, time=10:12:01.9681630
   WorkItem Thread : (Busy:5, Free:5, Min:8, Max:10)
執行緒[5]開始: ID=8, time=10:12:01.9792659
   WorkItem Thread : (Busy:6, Free:4, Min:8, Max:10)
執行緒[6]開始: ID=9, time=10:12:01.9902236
   WorkItem Thread : (Busy:7, Free:3, Min:8, Max:10)
執行緒[7]開始: ID=10, time=10:12:02.0011936
   WorkItem Thread : (Busy:8, Free:2, Min:8, Max:10)
執行緒[8]開始: ID=11, time=10:12:02.0111697
   WorkItem Thread : (Busy:8, Free:2, Min:8, Max:10)
執行緒[9]開始: ID=12, time=10:12:02.9363821
   WorkItem Thread : (Busy:9, Free:1, Min:8, Max:10)
執行緒[10]開始: ID=13, time=10:12:03.9363546
   WorkItem Thread : (Busy:10, Free:0, Min:8, Max:10)
執行緒[1]結束: ID=4, time=10:12:09.9380808
   WorkItem Thread : (Busy:10, Free:0, Min:8, Max:10)
執行緒[2]結束: ID=5, time=10:12:09.9470285
   WorkItem Thread : (Busy:9, Free:1, Min:8, Max:10)
執行緒[11]開始: ID=5, time=10:12:09.9479320
   WorkItem Thread : (Busy:9, Free:1, Min:8, Max:10)
執行緒[3]結束: ID=6, time=10:12:09.9580669
   WorkItem Thread : (Busy:9, Free:1, Min:8, Max:10)
執行緒[12]開始: ID=6, time=10:12:09.9587256
   WorkItem Thread : (Busy:9, Free:1, Min:8, Max:10)
執行緒[4]結束: ID=7, time=10:12:09.9690581
   WorkItem Thread : (Busy:9, Free:1, Min:8, Max:10)
執行緒[5]結束: ID=8, time=10:12:09.9800361
   WorkItem Thread : (Busy:8, Free:2, Min:8, Max:10)
執行緒[6]結束: ID=9, time=10:12:09.9910460
   WorkItem Thread : (Busy:7, Free:3, Min:8, Max:10)
執行緒[7]結束: ID=10, time=10:12:10.0020466
   WorkItem Thread : (Busy:6, Free:4, Min:8, Max:10)
執行緒[8]結束: ID=11, time=10:12:10.0120523
   WorkItem Thread : (Busy:5, Free:5, Min:8, Max:10)
執行緒[9]結束: ID=12, time=10:12:10.9370610
   WorkItem Thread : (Busy:4, Free:6, Min:8, Max:10)
執行緒[10]結束: ID=13, time=10:12:11.9370721
   WorkItem Thread : (Busy:3, Free:7, Min:8, Max:10)
執行緒[11]結束: ID=5, time=10:12:17.9500835
   WorkItem Thread : (Busy:2, Free:8, Min:8, Max:10)
執行緒[12]結束: ID=6, time=10:12:17.9600530
   WorkItem Thread : (Busy:1, Free:9, Min:8, Max:10)

提升執行緒集區預設產生執行緒緒數量

很多人想說,要解決幾乎每一秒種才能夠獲得一個新的執行緒方法很簡單呀,當處理程序一啟動之後,就預設產生出大量的執行緒出來,有人要一個執行緒,不就馬上就可以得到一個執行緒嗎?其實,問題沒有這麼簡單來解決,若產生出預設大量的執行緒,每個執行緒將會耗用至少 1MB 的記憶體空間,而且大量的執行緒在系統中執行,也會造成系統上因為這些大量正在執行中的執行緒存在,而會耗費系統資源來處理內容交換 Content Switch 的問題。
不過,不管如何,還是來體驗看看這樣的設計效果,請先註解這個敘述 ThreadPool.SetMaxThreads(10, 10); ,並且解除 ThreadPool.SetMinThreads(12, 12); 這個敘述的註解。
請按下 Ctrl + F5 來執行這個測試程式,現在將會發現到執行緒集區在處理程序啟動的時候,就已經準備好 12 可用執行緒,因此,這個測試程式突然間要求 12 個執行緒,執行緒集區就可以馬上提供這些執行緒以便執行相關並行工作。
執行緒集區的相關設定資訊
邏輯處理器數目 : 8
WorkItem Thread : (Busy:0, Free:32767, Min:12, Max:32767)
IOPC Thread : (Busy:0, Free:1000, Min:12, Max:1000)

準備產生出 12 個執行緒
請按下任一按鍵,進行執行緒集區的使用模擬
 執行緒[1]開始: ID=4, time=10:29:55.1252398
   WorkItem Thread : (Busy:1, Free:32766, Min:12, Max:32767)
執行緒[2]開始: ID=5, time=10:29:55.1354606
   WorkItem Thread : (Busy:3, Free:32764, Min:12, Max:32767)
執行緒[3]開始: ID=6, time=10:29:55.1464298
   WorkItem Thread : (Busy:4, Free:32763, Min:12, Max:32767)
執行緒[4]開始: ID=7, time=10:29:55.1574602
   WorkItem Thread : (Busy:5, Free:32762, Min:12, Max:32767)
執行緒[5]開始: ID=8, time=10:29:55.1684434
   WorkItem Thread : (Busy:6, Free:32761, Min:12, Max:32767)
執行緒[6]開始: ID=9, time=10:29:55.1793864
   WorkItem Thread : (Busy:7, Free:32760, Min:12, Max:32767)
執行緒[7]開始: ID=10, time=10:29:55.1904425
   WorkItem Thread : (Busy:8, Free:32759, Min:12, Max:32767)
執行緒[8]開始: ID=11, time=10:29:55.2014103
   WorkItem Thread : (Busy:9, Free:32758, Min:12, Max:32767)
執行緒[9]開始: ID=12, time=10:29:55.2125127
   WorkItem Thread : (Busy:10, Free:32757, Min:12, Max:32767)
執行緒[10]開始: ID=13, time=10:29:55.2234850
   WorkItem Thread : (Busy:10, Free:32757, Min:12, Max:32767)
執行緒[11]開始: ID=14, time=10:29:55.2344311
   WorkItem Thread : (Busy:12, Free:32755, Min:12, Max:32767)
執行緒[12]開始: ID=15, time=10:29:55.2453406
   WorkItem Thread : (Busy:12, Free:32755, Min:12, Max:32767)
執行緒[1]結束: ID=4, time=10:30:03.1263397
   WorkItem Thread : (Busy:12, Free:32755, Min:12, Max:32767)
執行緒[2]結束: ID=5, time=10:30:03.1363742
   WorkItem Thread : (Busy:11, Free:32756, Min:12, Max:32767)
執行緒[3]結束: ID=6, time=10:30:03.1473493
   WorkItem Thread : (Busy:10, Free:32757, Min:12, Max:32767)
執行緒[4]結束: ID=7, time=10:30:03.1583797
   WorkItem Thread : (Busy:9, Free:32758, Min:12, Max:32767)
執行緒[5]結束: ID=8, time=10:30:03.1693653
   WorkItem Thread : (Busy:8, Free:32759, Min:12, Max:32767)
執行緒[6]結束: ID=9, time=10:30:03.1803739
   WorkItem Thread : (Busy:7, Free:32760, Min:12, Max:32767)
執行緒[7]結束: ID=10, time=10:30:03.1913785
   WorkItem Thread : (Busy:6, Free:32761, Min:12, Max:32767)
執行緒[8]結束: ID=11, time=10:30:03.2023760
   WorkItem Thread : (Busy:5, Free:32762, Min:12, Max:32767)
執行緒[9]結束: ID=12, time=10:30:03.2133287
   WorkItem Thread : (Busy:4, Free:32763, Min:12, Max:32767)
執行緒[10]結束: ID=13, time=10:30:03.2244469
   WorkItem Thread : (Busy:3, Free:32764, Min:12, Max:32767)
執行緒[11]結束: ID=14, time=10:30:03.2353793
   WorkItem Thread : (Busy:2, Free:32765, Min:12, Max:32767)
執行緒[12]結束: ID=15, time=10:30:03.2473591
   WorkItem Thread : (Busy:1, Free:32766, Min:12, Max:32767)



沒有留言:

張貼留言