1、什麼是ThreadPool 線程池(源碼)html
1.線程池顧名思義,有咱們的系統建立一個容器裝載着咱們的線程,由CLR控制的全部AppDomain共享。線程池可用於執行任務、發送工做項、處理異步 I/O、表明其餘線程等待以及處理計時器。因此使用線程池不須要本身建立線程,而是經過線程池來建立和執行和管理線程。git
2、ThreadPool 線程池和線程的區別多線程
1.ThreadPool 線程池是在.NET 2.0出現的,是一個享元模式整個程序共同享用這一個線程池,當咱們的線程執行任務以後它不會馬上銷燬,它會回到線程池中,若是有新的任務它就會去執行。避免了咱們線程的重複建立和銷燬(也不會形成咱們CPU的上下文切換的損耗)。異步
2.你們仔細看一下我前面寫的Thread 建立線程執行任務以後,它會自動銷燬。那問題來了咱們常常的建立、銷燬線程這可都是資源的浪費呀!!因此咱們要利用每一個線程佔有的資源。ide
3、ThreadPool 線程池缺點性能
1.線程池在性能上優於線程,可是它也是有缺點的。它不支持線程的取消、完成、失敗通知等交互性操做。spa
2.它不能設置池中線程的Name,會增長使用者的難度。線程
3.線程池中線程一般都是後臺線程,優先級爲ThreadPriority.Normalcode
4.線程池堵塞會影響咱們的性能,阻塞會使CLR錯誤地認爲它佔用了大量CPU。CLR可以檢測或補償(往池中注入更多線程),可是這可能使線程池受到後續超負荷的印象。Task (也及時後面要講的)解決了這個問題。orm
5.線程池使用的是全局隊列,全局隊列中的線程依舊會存在競爭共享資源的狀況,從而影響性能(Task 解決了這個問題,方案是使用本地隊列)。
4、線程池工做原理
1.CLR初始化時,線程池中是沒有線程的,可是內部有一個操做請求隊列,當咱們的應用程序使用異步時,會將一個記錄項添加到線程池的隊列中,線程池隊列會自動讀取這個記錄項,而且發給一個線程池的線程,若是線程池沒有線程就會建立一個線程執行這任務,當線程完成任務它不會自動銷燬而是回到咱們的線程池中,等待線程池派發新的任務。
2.若是程序給線程池派發了不少任務,線程池也會使用這一個線程執行全部的任務,若是咱們的請求速度大於了咱們的線程處理速度,就會建立額外線程,就不會致使咱們建立了過多的線程。
2.當咱們的線程有大量休息的,它們會在一段時間內自動銷燬。這樣很好的控制了咱們應用程序的性能。
5、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(); }
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(); }