多執行緒之旅(ThreadPool 執行緒池)

  • 2020 年 3 月 30 日
  • 筆記

一、什麼是ThreadPool 執行緒池(源碼

      1.執行緒池顧名思義,有我們的系統創建一個容器裝載著我們的執行緒,由CLR控制的所有AppDomain共享。執行緒池可用於執行任務、發送工作項、處理非同步 I/O、代表其他執行緒等待以及處理計時器。所以使用執行緒池不需要自己創建執行緒,而是通過執行緒池來創建和執行和管理執行緒。

二、ThreadPool 執行緒池和執行緒的區別

      1.ThreadPool 執行緒池是在.NET 2.0出現的,是一個享元模式整個程式共同享用這一個執行緒池,當我們的執行緒執行任務之後它不會立刻銷毀,它會回到執行緒池中,如果有新的任務它就會去執行。避免了我們執行緒的重複創建和銷毀(也不會造成我們CPU的上下文切換的損耗)。

      2.大家仔細看一下我前面寫的Thread 創建執行緒執行任務之後,它會自動銷毀。那問題來了我們經常的創建、銷毀執行緒這可都是資源的浪費呀!!所以我們要利用每個執行緒佔有的資源。

三、ThreadPool 執行緒池缺點

       1.執行緒池在性能上優於執行緒,但是它也是有缺點的。它不支援執行緒的取消、完成、失敗通知等交互性操作。

       2.它不能設置池中執行緒的Name,會增加使用者的難度。

  3.執行緒池中執行緒通常都是後台執行緒,優先順序為ThreadPriority.Normal

  4.執行緒池堵塞會影響我們的性能,阻塞會使CLR錯誤地認為它佔用了大量CPU。CLR能夠檢測或補償(往池中注入更多執行緒),但是這可能使執行緒池受到後續超負荷的印象。Task (也及時後面要講的)解決了這個問題。

  5.執行緒池使用的是全局隊列,全局隊列中的執行緒依舊會存在競爭共享資源的情況,從而影響性能(Task 解決了這個問題,方案是使用本地隊列)。

四、執行緒池工作原理

  1.CLR初始化時,執行緒池中是沒有執行緒的,但是內部有一個操作請求隊列,當我們的應用程式使用非同步時,會將一個記錄項添加到執行緒池的隊列中,執行緒池隊列會自動讀取這個記錄項,並且發給一個執行緒池的執行緒,如果執行緒池沒有執行緒就會創建一個執行緒執行這任務,當執行緒完成任務它不會自動銷毀而是回到我們的執行緒池中,等待執行緒池派發新的任務。

  2.如果程式給執行緒池派發了很多任務,執行緒池也會使用這一個執行緒執行所有的任務,如果我們的請求速度大於了我們的執行緒處理速度,就會創建額外執行緒,就不會導致我們創建了過多的執行緒。

  2.當我們的執行緒有大量休息的,它們會在一段時間內自動銷毀。這樣很好的控制了我們應用程式的性能。

五、ThreadPool 執行緒池使用

  1.ThreadPool是一個靜態類,調用QueueUserWorkItem方法,是可以將一個非同步計算放入我們的執行緒池隊列中。

public static bool QueueUserWorkItem(WaitCallback callBack);  public static bool QueueUserWorkItem(WaitCallback callBack, object state);

方法 說明
QueueUserWorkItem 啟動執行緒池裡的一個執行緒(工作者執行緒)
GetMinThreads 檢索執行緒池在新請求預測中能夠按需創建的執行緒的最小數量。
GetMaxThreads 最多可用執行緒數,所有大於此數目的請求將保持排隊狀態,直到執行緒池執行緒由空閑。
GetAvailableThreads 剩餘空閑執行緒數。
SetMaxThreads 設置執行緒池中的最大執行緒數(請求數超過此值則進入隊列)。
SetMinThreads 設置執行緒池最少需要保留的執行緒數。

 

 

 

 

 

 

  2.我們可以看到ThreadPool比Thread少了很多的API,被砍掉了

/// <summary>          /// ThreadPool的使用          /// workerThreads  CLR執行緒池分為工作者執行緒(workerThreads)          /// completionPortThreads I/O執行緒(completionPortThreads)          /// </summary>          public static void Show()          {              //使用執行緒              ThreadPool.QueueUserWorkItem((x) => Running());              ThreadPool.GetAvailableThreads(out int workerThreads, out int completionPortThreads);              Console.WriteLine($"沒有設置執行緒數之前 workerThreads:{workerThreads}  completionPortThreads:{completionPortThreads}");              //設置最大的執行緒數              ThreadPool.SetMaxThreads(16, 16);              //設置最小的執行緒數              ThreadPool.SetMinThreads(8, 8);              ThreadPool.GetAvailableThreads(out int workerThreads1, out int completionPortThreads1);              Console.WriteLine($"設置最大的執行緒數之後 workerThreads:{workerThreads1}  completionPortThreads:{completionPortThreads1}");              Console.ReadLine();          }

View Code

   3.我們要注意的就是堵塞執行緒的時候一定要做好處理,最好是不要堵塞我們的執行緒,不然很容易造成死鎖GG

        /// <summary>          /// ThreadPool 執行緒等待          ///類  包含了一個bool屬性          ///false--WaitOne等待--Set--true--WaitOne直接過去          ///true--WaitOne直接過去--ReSet--false--WaitOne等待          ///https://www.cnblogs.com/howtrace/p/11362284.html          /// </summary>          public static void Show1()          {              //設置最大的執行緒數              ThreadPool.SetMaxThreads(16, 16);              //設置最小的執行緒數              ThreadPool.SetMinThreads(8, 8);              //設置false使用WaitOne()會直接堵塞執行緒,不會釋放 、Set()設置為true              ManualResetEvent manualResetEvent = new ManualResetEvent(false);              //設置false使用WaitOne()會直接堵塞執行緒,不會釋放 、Set()設置為true              AutoResetEvent autoResetEvent = new AutoResetEvent(false);              //上面兩種方法都是可以攔截執行緒,都是繼承EventWaitHandle 介面              //就都具有Reset() //紅燈 設置為false導致執行緒等待              //Set() //綠燈 設置為true 啟動執行緒繼續執行              //WaitOne() // 等待訊號 會根據我們執行緒狀態執行,為true不需要等待直接執行              //反之為false會等待執行緒狀態為true才會執行                //不同點 ManualResetEvent AutoResetEvent              //ManualResetEvent 在·使用Set()的時候會所有處理 WaitOne 狀態執行緒均繼續執行。              //AutoResetEvent 在使用Set()的時候會執行一個執行緒其他的執行緒繼續等待執行。                for (int i = 0; i < 20; i++)              {                  var k = i;                  ThreadPool.QueueUserWorkItem(x =>                  {                      Console.WriteLine(k);                      if (k < 18)                      {                          //等待執行緒,但是上面我們只開了16個執行緒,結果我18個執行緒全部等待                          //導致了死鎖                          manualResetEvent.WaitOne();                      }                      else                      {                          //恢復執行狀態                          manualResetEvent.Set();                      }                  });                  if (manualResetEvent.WaitOne())                  {                      Console.WriteLine("沒有死鎖、、、");                  }                  Console.WriteLine("等著QueueUserWorkItem完成後才執行");              }              Console.ReadLine();          }

View Code