ThreadPool(線程池)介紹

来源:https://www.cnblogs.com/BigBrotherStone/archive/2020/01/30/12242799.html
-Advertisement-
Play Games

" 返回《C 併發編程》" "1. 線程池的由來" "1.1. 線程池出現前" "1.2. 線程池的誕生" "1.3. CLR線程池工作過程" "2. 線程池解決的問題" "2.1. 非同步調用方法" "2.2. 按時間間隔調用方法" "3. 當單個內核對象接收到信號通知時調用方法" "3.1. 註冊 ...


>>返回《C# 併發編程》

1. 線程池的由來

1.1. 線程池出現前

解決三個需求

  1. 非同步調用方法
  2. 按時間間隔調用方法
  3. 當一個內核對象收到信號時調用方法

開發人員經常創建一個新線程來執行單個任務,當任務完成時,該線程就會死亡。

  • 與進程相比,創建和銷毀線程速度更快,並且占用的OS資源更少,但是創建銷毀線程肯定不是免費的
  • 創建線程過程
    1. 分配和初始化內核對象,分配和初始化線程的堆棧記憶體
    2. Windows 向進程中的每個DLL發送 DLL_THREAD_ATTACH 通知,從而導致磁碟中的pages被轉移到記憶體中以便代碼可以執行
  • 銷毀線程過程
    • 當線程死亡時,將向每個DLL發送 DLL_THREAD_DETACH 通知,該線程的堆棧記憶體被釋放,並且內核對象被釋放(如果其使用計數變為0
  • 因此,創建和銷毀線程有很多開銷,這些開銷與創建線程最初要執行的工作無關

1.2. 線程池的誕生

  • Microsoft實現了一個線程池,該線程池首次在 Windows2000 中獲得支持。
  • 當 .NET Framework 團隊設計和構建公共語言運行庫(CLR)時,他們決定在CLR本身中實現一個線程池
    • 這樣,任何托管應用程式都可以利用線程池,即使該應用程式運行在Windows 2000之前的Windows版本(例如Windows 98)上。

1.3. CLR線程池工作過程

線程池執行任務流程

  • CLR初始化時,其線程池不包含任何線程。
  • 當應用程式要創建線程來執行任務時,應用程式應請求該任務由線程池線程執行。
  • 線程池知道後,將創建一個初始線程。這個新線程將與其他任何線程進行相同的初始化。
  • 當任務完成時,線程不會銷毀自己。而是,線程將進入掛起(suspended)狀態下返回線程池。
  • 線程池已有線程能滿足運算需求
    • 如果應用程式再次請求線程池,則被掛起的線程將被喚醒執行任務,不會創建新線程。
    • 只要應用程式將任務列入到線程池的速度不超過一個線程處理每個列入的任務的速度,就節省了線程創建銷毀產生的開銷
  • 線程池已有線程不能滿足運算需求
    • 如果應用程式將任務列入到線程池的速度超過一個線程處理每個列入的任務的速度,則線程池將創建其他線程。
    • 當然,創建新線程確實會產生開銷,但是應用程式很可能僅需要幾個線程來處理應用程式生命周期內向它拋出的所有任務
  • 線程池線程超過需要需要的算力
    • 當線程池線程自身掛起時,如果一段時間(如40秒)沒有被使用時,線程將喚醒並自行銷毀
      • 從而釋放它正在使用的所有OS資源(堆棧,內核對象等)
  • 總的來說,使用線程池可以提高應用程式的性能。

線程池提供了四種功能:

  • 非同步調用方法
  • 按時間間隔調用方法
  • 當發信號通知單個內核對象時調用方法
  • 非同步I/O請求完成時調用方法
    • 應用程式開發人員很少使用這個功能,因此在此不做說明

要為線程池中的任務排隊,請使用 System.Threading 命名空間中定義的 ThreadPool 靜態類。

2. 線程池解決的問題

2.1. 非同步調用方法

要使線程池線程非同步調用方法,您的代碼必須調用 ThreadPoolQueueUserWorkItem 方法,如下所示:

public static Boolean QueueUserWorkItem(WaitCallback wc, Object state);
public static Boolean QueueUserWorkItem(WaitCallback wc); 

這些方法將“工作項”(和可選的狀態數據)排隊到線程池中的線程,然後立即返回。

  • “工作項”只是一個委托
    • System.Threading.WaitCallback 委托類型定義如下:

      public delegate void WaitCallback(Object state);
  • “狀態數據”是一個 Object 類型的數據,作為參數傳遞給“工作項”委托
    • 沒有 “狀態數據” 參數的QueueUserWorkItem版本將 null 傳遞給WaitCallback方法
  • 最終,池中的某些線程將處理工作項,從而導致您的方法被調用

CLR的線程池將在必要時自動創建線程,併在可能的情況下重用現有線程。該線程在處理回調方法後不會立即被銷毀。它返回線程池,以便準備處理隊列中的任何其他工作項

線程池調用非同步方法

using System;
using System.Threading;

class App {
   static void Main() {
      Console.WriteLine("Main thread: 列入一個非同步操作.");
      ThreadPool.QueueUserWorkItem(new WaitCallback(MyAsyncOperation));

      Console.WriteLine("Main thread: 執行其他操作.");
      // ...

      Console.WriteLine("Main thread: 暫停在這,以模擬執行其他操作。");
      Console.ReadLine();
   }

   static void MyAsyncOperation(Object state) {
      Console.WriteLine("ThreadPool thread: 執行非同步操作.");
      // ...
      Thread.Sleep(5000);    
      // 等待5s,模擬執行工作項
      // 方法返回,導致線程掛起自身,以等待其他“工作項”
   }
}

輸出為:

Main thread: 列入一個非同步操作.
Main thread: 執行其他操作.
Main thread: 暫停在這,以模擬執行其他操作。
ThreadPool thread: 執行非同步操作.

2.2. 按時間間隔調用方法

System.Threading 命名空間定義了 Timer 類。當構造 Timer 類的實例時,您在告訴線程池您希望在將來的特定時間回調您的方法。 Timer 類提供了四個構造函數:

public Timer(TimerCallback callback, Object state,
   Int32 dueTime, Int32 period);
public Timer(TimerCallback callback, Object state,
   UInt32 dueTime, UInt32 period);
public Timer(TimerCallback callback, Object state,
   Int64 dueTime, Int64 period);
public Timer(TimerCallback callback, Object state,
   Timespan dueTime, TimeSpan period); 

System.Threading.TimerCallback 委托類型定義如下:

public delegate void TimerCallback(Object state);
  • 構造傳遞的委托調用時將構造傳遞的Object對象 state 作為參數傳遞
  • 可以使用dueTime參數來告訴線程池第一次調用回調方法之前要等待多少毫秒。
    • 立即調用回調方法設置 dueTime 參數為 0
    • 防止調用回調方法設置 dueTime 參數為 Timeout.Infinite0
  • 參數 period 允許您指定每個連續調用之前等待的時間(以毫秒為單位)
    • 為0時,線程池將僅調用一次回調方法。
  • 構造 Timer 對象後,線程池自動監視時間
    • Timer類提供了一些方法,使您可以與線程池進行通信,以修改何時回調該方法
    • ChangeDispose 方法
      cs public Boolean Change(Int32 dueTime, Int32 period); public Boolean Change(UInt32 dueTime, UInt32 period); public Boolean Change(Int64 dueTime, Int64 period); public Boolean Change(TimeSpan dueTime, TimeSpan period); public Boolean Dispose(); public Boolean Dispose(WaitHandle notifyObject);
      • Change 方法使您可以更改 Timer對象dueTimeperiod
      • 使用 Dispose 方法可以完全和有選擇的取消回調
        • 當所有正在執行的回調完成後,通過notifyObject參數,發送信號到內核對象,取消後續執行

每2000毫秒(或2秒)調用一次方法示例:

using System;
using System.Threading;

class App {
   static void Main() {
      Console.WriteLine("每2秒檢查一次修改狀態.");
      Console.WriteLine("   (按 Enter 鍵停止示常式序)");
      Timer timer = new Timer(new TimerCallback(CheckStatus), null, 0, 2000);
      Console.ReadLine();
   }

   static void CheckStatus(Object state) {
      Console.WriteLine("檢查狀態.");
      // ...
   }
}

輸出為:

每2秒檢查一次修改狀態.
   (按 Enter 鍵停止示常式序)
檢查狀態.
檢查狀態.
檢查狀態.
... ...

3. 當單個內核對象接收到信號通知時調用方法

在進行性能研究時,Microsoft研究人員發現許多應用程式生成線程,只是為了等待單個內核對象接收發出的信號。

對象發出信號後,線程將某種通知發佈到另一個線程,然後返回回來以等待對象再次發出信號。

一些開發人員甚至編寫了其中多個線程各自等待一個對象的代碼。這是對系統資源的極大浪費。
因此,如果您的應用程式中當前有等待單個內核對象發出信號的線程,那麼線程池再次是您提高應用程式性能的理想資源。

3.1. 註冊WaitHandle

System.Threading.ThreadPool 類中定義的一些靜態方法(RegisterWaitForSingleObject)。要使線程池線程在發出內核對象信號時調用方法

public static RegisterWaitHandle RegisterWaitForSingleObject(
   WaitHandle waitObject, WaitOrTimerCallback callback, Object state, 
   UInt32 milliseconds, Boolean executeOnlyOnce);

public static RegisterWaitHandle RegisterWaitForSingleObject(
   WaitHandle waitObject, WaitOrTimerCallback callback, Object state, 
   Int32 milliseconds, Boolean executeOnlyOnce);

public static RegisterWaitHandle RegisterWaitForSingleObject(
   WaitHandle waitObject, WaitOrTimerCallback callback, Object state, 
   TimeSpan milliseconds, Boolean executeOnlyOnce);

public static RegisterWaitHandle RegisterWaitForSingleObject(
   WaitHandle waitObject, WaitOrTimerCallback callback, Object state,
   Int64 milliseconds, Boolean executeOnlyOnce);

當您調用RegisterWaitForSingleObject方法時

  • waitObject 參數,需要線程池等待的內核對象
    • 可以將引用傳遞給AutoResetEvent,ManualResetEvent或Mutex對象
  • callback 參數,需要線程池線程調用的方法
    • System.Threading.WaitOrTimerCallback 委托類型定義:

      public delegate void WaitOrTimerCallback(Object state, Boolean timedOut);
  • state 參數, callback 委托對象運行時需要的 state 參數
    • 如果不需要可以為 null
  • milliseconds參數, 單位:毫秒,需要線程池等待多久後向內核對象發出超時信號
    • 通常在此處傳遞-1表示無限超時
  • executeOnlyOnce參數
    • true ,則線程池線程將僅執行一次回調方法,之後線程將不再在 waitObject 參數上等待
    • false ,則每次向內核對象發出信號時,線程池線程都會執行回調方法(這對於AutoResetEvent對象最有用)
      • 表示每次完成等待操作後都重置計時器,直到 waitObject 取消註冊

調用WaitOrTimerCallback委托類型的回調方法,bool類型的 timedOut 參數值:

  • false ,說明內核對象收到信號,導致該方法被調用
  • true ,說明在指定的時間內沒有發信號通知內核對象,超時後導致該方法被調用
    • 回調方法應執行必要的操作(超時處理)

3.2. 註銷 WaitHandle

  • 在前提到的 RegisterWaitForSingleObject 方法返回一個 RegisteredWaitHandle 對象
  • 該對象標識線程池正在等待的內核對象
    • 如果由於某種原因您的應用程式想要告訴線程池停止監視已註冊的 WaitHandle ,則您的應用程式可以調用 RegisteredWaitHandleUnregister 方法:
      cs public Boolean Unregister(WaitHandle waitObject);
    • waitObject 參數,指示當所有排隊的工作項均已執行時,如何通知您
      • 如果您不想收到通知,則應為此參數傳遞 null
      • 如果將派生自 WaitHandle 的對象引用傳遞給Unregister方法,當已註冊的WaitHandle的所有待處理工作項均已執行時,線程池將向該對象;。;(waitObject)發出信號

3.3. 代碼示例

using System;
using System.Threading;

class App {
   static void Main() {
      AutoResetEvent are = new AutoResetEvent(false);
      RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(
         are, new WaitOrTimerCallback(EventSignalled), null, 1100, false);
      for (Int32 x = 0 ; x < 5; x++) {
         Thread.Sleep(1000);
         are.Set();
      }
      
      Thread.Sleep(2400);

      rwh.Unregister(null);
      Console.WriteLine("按 Enter 鍵停止示常式序");
      Console.ReadLine();
   }

   static void EventSignalled(Object state, Boolean timedOut) {
      if (timedOut) {
         Console.WriteLine("等待 AutoResetEvent 操作超時.");
      } else {
         Console.WriteLine("AutoResetEvent 接到信號.");
      }
   }
}

輸出為:

; 五次 AutoResetEvent 發送信號
AutoResetEvent 接到信號.
AutoResetEvent 接到信號.
AutoResetEvent 接到信號.
AutoResetEvent 接到信號.
AutoResetEvent 接到信號.
; 等待2.4s導致兩次超時
等待 AutoResetEvent 操作超時.
等待 AutoResetEvent 操作超時.
; 註銷後不在調用委托
按 Enter 鍵停止示常式序

4. 結語

關於線程池相關的 API 不需要熟練掌握,但是我們瞭解了線程池的設計初衷和具備的功能,為我們更好的理解 非同步編程、並行、多線程 有很大的助益。

前一章我們瞭解了 同步上下文 是如何編排線程執行代碼的,這一章我們瞭解了線程池,加深了非同步任務交給線程池執行的理解,後面我們開始對 非同步編程、數據流塊、Rx等我們常用的技術進行講解。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 進入命令行視窗,開始菜單搜索框輸入“cmd”即可,如圖1-18所示。在視窗中輸入命令“java -version ”,回車。出現如下結果,則說明JDK安裝成功。 設置環境變數——驗證JDK安裝和配置是否成功 作者:流浪者 日期:2020-01-30 ...
  • 環境變數是在操作系統中一個具有特定名字的對象, 它包含了一個或者多個應用程式所將使用到的信息。 Path是一個常見的環境變數,它告訴操作系統,當要求系統運行一個程式而沒有告訴它程式所在的完整路徑時,系統除了在當前目錄下尋找此程式外,還應到哪些目錄下尋找。 ·設置Path環境變數的步驟如下 1. 右鍵 ...
  • ·下載JDK 1. 下載地址,點擊進入: www.oracle.com/technetwork/java/javase/downloads/index.html 點擊下載JDK,會出現圖1-9所示的下載列表界面。首先,點擊”Accept License Agreement”,然後選擇對應的版本,下載 ...
  • JVM(Java Virtual Machine)就是一個虛擬的用於執行bytecode位元組碼的”虛擬電腦”。他也定義了指令集、寄存器集、結構棧、垃圾收集堆、記憶體區域。JVM負責將Java位元組碼解釋運行,邊解釋邊運行,這樣,速度就會受到一定的影響。 不同的操作系統有不同的虛擬機。Java 虛擬機機 ...
  • 電腦高級語言的類型主要有編譯型(如:C和C++)和解釋型(如:PHP、Python)兩種,而Java 語言是兩種類型的結合。 Java首先利用文本編輯器編寫 Java源程式,源文件的尾碼名為.java;再利用編譯器(javac)將源程式編譯成位元組碼文件,位元組碼文件的尾碼名為.class; 最後利用 ...
  • 這是Serilog系列的第三篇文章。 1. "第1部分 使用Serilog RequestLogging減少日誌詳細程度" 2. "第2部分 使用Serilog記錄所選的終結點屬性" 3. 第3部分 使用Serilog.AspNetCore記錄MVC屬性(本文) 4. 第4部分 從Serilog請 ...
  • WPF視窗充滿了各種元素,但這些元素中只有一部分是控制項。在WPF領域,控制項通常被描述為與用戶交互的元素——能接收焦點並接受鍵盤或滑鼠輸入的元素。明顯的例子包括文本框和按鈕。然而,這個區別有時有些模糊。將工具提示視為控制項,因為它根據用戶滑鼠的移動顯示或消失。將標簽視為控制項,因為它支持記憶碼(mnemo ...
  • 多點觸控(multi-touch)是通過觸摸屏幕與應用程式進行交互的一種方式。多點觸控輸入和更傳統的基於筆(pen-based)的輸入的區別是多點觸控識別手勢(gesture)——用戶可移動多根手指以執行常見操作的特殊方式。例如,在觸摸屏上放置兩根手指並同時移動他們,這通常意味著“放大",而以一根手 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...