C# 多線程(三)線程池

????????

???????

目錄

1.通過TPL使用線程池? ??

2.不使用TPL進入線程池的辦法

異步委托

3.線程池優化技術

最小線程數的工作原理


?????????每當啟動一個新線程時,系統都需要花費數百微秒來分配資源,例如創建獨立的局部變量棧空間。默認情況下,每個線程還會占用約1MB內存。線程池通過共享和回收線程來消除這些開銷,使得多線程技術可以應用于非常細粒度的場景而不會造成性能損失。這在利用多核處理器以"分而治之"方式并行執行計算密集型代碼時尤為有用。

線程池還會限制同時運行的線程總數。過多的活動線程會給操作系統帶來管理負擔,并導致CPU緩存失效。一旦達到限制,新任務將進入隊列,只有當前線程完成任務后才能啟動。這使得高并發應用(如Web服務器)的實現成為可能。(異步方法模式是一種更高級的技術,它能更高效地利用線程池中的線程,我們會在后面文章講解)。

進入線程池有多種方式:

  • Task Parallel Library (Framework 4.0)
  • ThreadPool.QueueUserWorkItem
  • asynchronous delegates
  • BackgroundWorker

以下組件會間接使用線程池:
? WCF、遠程處理(Remoting)、ASP.NET和ASMX Web服務應用服務器
? System.Timers.Timer和System.Threading.Timer計時器
? 以Async結尾的框架方法(如WebClient基于事件的異步模式)
? 大多數BeginXXX方法(異步編程模型模式)
? PLINQ并行查詢

任務并行庫(TPL)和PLINQ功能強大且抽象層次高,即使不考慮線程池優勢也值得用于多線程開發。使用線程池時需注意以下事項:

  1. 無法設置線程池線程的Name屬性,這會增加調試難度(但在Visual Studio線程窗口中可附加描述信息)

  2. 線程池線程默認都是后臺線程(通常不影響使用)

  3. 在應用初期阻塞線程池線程可能導致額外延遲,除非調用ThreadPool.SetMinThreads(參見"優化線程池"章節)

  4. 可臨時修改線程池線程優先級,但釋放回池后優先級會自動重置為普通級別

可通過Thread.CurrentThread.IsThreadPoolThread屬性檢測當前是否運行在線程池線程上。

1.通過TPL使用線程池? ??

????????使用任務并行庫(TPL)中的Task類可以輕松進入線程池。Task類是在.NET Framework 4.0中引入的:如果您熟悉舊的結構,可以將非泛型Task類視為ThreadPool.QueueUserWorkItem的替代品,而泛型Task<TResult>則是異步委托的替代品。新結構比舊結構更快、更方便、更靈活。

要使用非泛型Task類,只需調用Task.Factory.StartNew并傳入目標方法的委托即可:

static void Main()    // The Task class is in System.Threading.Tasks
{Task.Factory.StartNew (Go);
}static void Go()
{Console.WriteLine ("Hello from the thread pool!");
}

Task.Factory.StartNew函數返回一個Task類型對象,你可以使用這個對象管理這個任務,比如你可以用Waith方法等待任務完成。

注意:當調用任務的Wait方法時,任何未處理的異常都會便捷地重新拋回到宿主線程(如果不調用Wait而是直接放棄任務,未處理的異常將像普通線程一樣導致進程關閉)。

????????泛型Task<TResult>類是非泛型Task的子類,它允許在任務執行完成后獲取返回值。在下面的示例中,我們使用Task<TResult>下載網頁:

static void Main()
{// Start the task executing:Task<string> task = Task.Factory.StartNew<string>( () => DownloadString ("http://www.linqpad.net") );// We can do other work here and it will execute in parallel:RunSomeOtherMethod();// When we need the task's return value, we query its Result property:// If it's still executing, the current thread will now block (wait)// until the task finishes:string result = task.Result;
}static string DownloadString (string uri)
{using (var wc = new System.Net.WebClient())return wc.DownloadString (uri);
}

當查詢任務的Result屬性時,任何未處理的異常都會自動重新拋出(封裝在AggregateException中)。但如果不查詢Result屬性(也不調用Wait方法),未處理的異常將會導致進程崩潰。

任務并行庫(TPL)功能遠不止于此,它特別適合利用多核處理器優勢。我會在后續文章專門討論TPL的更多功能。

2.不使用TPL進入線程池的辦法

????????如果您的開發目標是.NET Framework 4.0之前的版本,則無法使用任務并行庫(TPL)。此時必須改用以下傳統方式進入線程池:ThreadPool.QueueUserWorkItem和異步委托。兩者的主要區別在于:

  1. 異步委托允許從線程返回數據

  2. 異步委托還能將異常封送回調用方

QueueUserWorkItem使用方法:
只需調用該方法并傳入要在池線程上執行的委托即可:

static void Main()
{ThreadPool.QueueUserWorkItem (Go);ThreadPool.QueueUserWorkItem (Go, 123);Console.ReadLine();
}static void Go (object data)   // data will be null with the first call.
{Console.WriteLine ("Hello from the thread pool! " + data);
}

運行結果:

Hello from the thread pool!
Hello from the thread pool! 123

目標方法Go必須接受單個object參數(以滿足WaitCallback委托)。這提供了傳遞數據的便捷方式,類似于ParameterizedThreadStart。但與Task不同,QueueUserWorkItem不會返回對象來幫助后續執行管理。此外,您必須顯式處理目標代碼中的異常——未處理的異常將導致程序崩潰。

異步委托


????????ThreadPool.QueueUserWorkItem未提供簡單機制來獲取線程執行完成后的返回值。異步委托調用(簡稱異步委托)解決了這個問題,允許任意數量的類型化參數雙向傳遞。更重要的是,異步委托上的未處理異常會便捷地重新拋回到原始線程(更準確地說,是調用EndInvoke的線程),因此不需要顯式處理。

以下是使用異步委托啟動工作任務的步驟:

  1. 實例化指向要在并行中運行方法的委托(通常使用預定義的Func委托)

  2. 調用委托的BeginInvoke,保存返回的IAsyncResult值BeginInvoke會立即返回,此時可執行其他操作

  3. 需要結果時,在委托上調用EndInvoke并傳入保存的IAsyncResult對象

下例使用異步委托調用與主線程并發執行一個返回字符串長度的簡單方法:

static void Main()
{Func<string, int> method = Work;IAsyncResult cookie = method.BeginInvoke ("test", null, null);//// ... here's where we can do other work in parallel...//int result = method.EndInvoke (cookie);Console.WriteLine ("String length is: " + result);
}static int Work (string s) { return s.Length; }

EndInvoke 主要完成三個關鍵操作:

  1. 等待異步委托完成執行(若尚未完成)

  2. 接收返回值(以及所有ref/out參數)

  3. 將工作線程中未處理的異常拋回調用線程

技術細節說明:
? 即使異步委托調用的方法沒有返回值,嚴格來說仍需調用EndInvoke
? 實際上這一要求存在爭議——畢竟沒有"EndInvoke執法者"來懲罰違規者!
? 但若選擇不調用EndInvoke,則必須自行處理工作方法的異常,避免靜默失敗

高級用法:
調用BeginInvoke時還可指定回調委托——即接受IAsyncResult參數的完成回調方法。這種模式允許發起線程"忘記"異步委托,但需要在回調端做一些額外工作:

static void Main()
{Func<string, int> method = Work;method.BeginInvoke ("test", Done, method);// ...//
}static int Work (string s) { return s.Length; }static void Done (IAsyncResult cookie)
{var target = (Func<string, int>) cookie.AsyncState;int result = target.EndInvoke (cookie);Console.WriteLine ("String length is: " + result);
}

BeginInvoke 的最后一個參數是用戶狀態對象,該對象會填充 IAsyncResult 的 AsyncState 屬性。這個參數可以傳遞任意您需要的數據;在本例中,我們用它向完成回調傳遞方法委托,以便我們能夠對其調用 EndInvoke。

3.線程池優化技術

????????線程池初始時僅包含一個線程。當任務被分配時,池管理器會"注入"新線程以應對額外的并發工作負載,直至達到最大限制。在持續空閑足夠長時間后,如果池管理器判斷減少線程能提升吞吐量,則可能"回收"多余線程。

您可以通過ThreadPool.SetMaxThreads設置線程池創建線程的上限,各版本默認值為:
????????? Framework 4.0(32位環境):1023個線程
????????? Framework 4.0(64位環境):32768個線程
????????? Framework 3.5:每個核心250個線程
????????? Framework 2.0:每個核心25個線程
(具體數值可能因硬件和操作系統而異)。設置較高數量是為了確保當部分線程阻塞時(如等待遠程計算機響應),程序仍能繼續執行。

????????通過ThreadPool.SetMinThreads還可設置下限線程數。下限的作用更為精妙:這是一種高級優化技術,指示池管理器在達到下限前不得延遲線程分配。當存在阻塞線程時,提高最小線程數可增強并發性(參見邊欄說明)。

????????默認下限為每個處理器核心1個線程——這是實現CPU完全利用的最低要求。但在服務器環境(如IIS下的ASP.NET)中,下限通常高得多,可達50個甚至更多。

最小線程數的工作原理

????????將線程池的最小線程數提升至x,并不會立即強制創建x個線程——線程僅在需要時才會創建。實際上,這個設置是指導線程池管理器在需要時可立即創建最多x個線程。那么問題來了:為什么線程池在需要線程時會有意延遲創建呢?

原因在于防止短暫爆發的短期活動導致線程全量分配,從而突然增加應用程序的內存占用。舉例來說,假設一臺四核計算機運行的客戶端應用一次性提交40個任務:

  • 若每個任務執行10毫秒的計算

  • 假設工作均勻分配到四個核心

  • 整個過程將在100毫秒內完成

理想情況下,我們希望40個任務正好運行在4個線程上:

  • 少于4個線程無法充分利用所有核心

  • 多于4個線程會浪費內存和CPU時間創建不必要的線程
    而這正是線程池的實際工作方式。使線程數與核心數匹配,既能保持較小的內存占用,又不會影響性能——前提是線程被高效利用(本例即是如此)。

但當每個任務改為查詢網絡(等待半秒響應且本地CPU閑置)時,線程池的節約策略就會失效。此時創建更多線程讓所有網絡查詢并發執行反而更高效。

為此線程池準備了備用方案:如果任務隊列持續半秒未變化,就會以每半秒一個的速度新增線程,直到達到線程池容量上限。

這半秒延遲是把雙刃劍:

  • 優勢:防止一次性短期活動導致程序突然多占用40MB(或更多)內存

  • 劣勢:當線程阻塞時(如數據庫查詢或調用WebClient.DownloadFile)可能造成不必要延遲

因此可以通過SetMinThreads告知線程池不要延遲創建前x個線程,例如:

//(第二個參數指定分配給I/O完成端口的線程數,該機制用于異步編程模型)
ThreadPool.SetMinThreads (50, 50);

默認值為每個處理器核心1個線程。


本小節完......

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/87358.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87358.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87358.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

學習筆記(29):訓練集與測試集劃分詳解:train_test_split 函數深度解析

學習筆記(29):訓練集與測試集劃分詳解&#xff1a;train_test_split 函數深度解析 一、為什么需要劃分訓練集和測試集&#xff1f; 在機器學習中&#xff0c;模型需要經歷兩個核心階段&#xff1a; 訓練階段&#xff1a;用訓練集數據學習特征與目標值的映射關系&#xff08;…

【全網唯一】自動化編輯器 Windows版純本地離線文字識別插件

目的 自動化編輯器超輕量級RPA工具&#xff0c;零代碼制作RPA自動化任務&#xff0c;解放雙手&#xff0c;釋放雙眼&#xff0c;輕松玩游戲&#xff0c;刷任務。本篇文章主要講解下自動化編輯器的TomatoOCR純本地離線文字識別Windows版插件如何使用和集成。 準備工作 1、下載自…

GitHub 2FA綁定

GitHub 2FA綁定 作為全球最大的代碼托管平臺&#xff0c;GitHub對賬號安全的重視程度不斷提升——自2023年3月起&#xff0c;GitHub已要求所有在GitHub.com上貢獻代碼的用戶必須啟用雙因素身份驗證&#xff08;2FA&#xff09;。如果你是符合條件的用戶&#xff0c;會收到一封…

pytest fixture基礎大全詳解

一、介紹 作用 fixture主要有兩個作用&#xff1a; 復用測試數據和環境&#xff0c;可以減少重復的代碼&#xff1b;可以在測試用例運行前和運行后設置和清理資源&#xff0c;避免對測試結果產生影響&#xff0c;同時也可以提高測試用例的運行效率。 優勢 pytest框架的fix…

Unity知識點-Renderer常用材質變量

本篇總結了Unity中renderer的3種常用的材質相關的變量&#xff1a;renderer.material,renderer.sharedMaterial,renderer.MaterialPropertyBlock。以及三者對SRPBatcher的影響。 一.介紹及對比 1.概念介紹 1.material 定義&#xff1a;material 是Render組件&#xff08;如…

【算法】??如何判斷時間復雜度?

文章目錄 1. 什么是時間復雜度&#xff1f;為什么需要時間復雜度&#xff1f; 2. 常見時間復雜度對比3. 如何分析時間復雜度&#xff1f;&#xff08;Java版&#xff09;&#x1f539; 步驟1&#xff1a;找出基本操作&#x1f539; 步驟2&#xff1a;分析循環結構&#xff08;1…

MySQL使用C語言連接

文章目錄 版本查看以及編譯mysql接口介紹初始化鏈接數據庫下發mysql命令mysql_query獲取執行結果mysql_store_result獲取結果行數mysql_num_rows獲取結果列數mysql_num_fields獲取列名mysql_fetch_fields獲取結果內容mysql_fetch_row關閉mysql鏈接mysql_closeC語言操作mysql查看…

堅持每日Codeforces三題挑戰:Day 7 - 題目詳解(2025-06-11,難度:1200,1300,1500)

每天堅持寫三道題第七天&#xff1a; Problem - A - Codeforces 1200 Problem - B - Codeforces 1300 Problem - A - Codeforces 1500 目錄 題目一: 題目大意: 解題思路: 代碼(C): 題目二: 題目大意: 解題思路: 代碼(C): 題目三: 題目大意: 解題思路: 代碼(C): …

洛谷 P4305:[JLOI2011] 不重復數字 ← unordered_set

【題目來源】 https://www.luogu.com.cn/problem/P4305 【題目描述】 給定 n 個數&#xff0c;要求把其中重復的去掉&#xff0c;只保留第一次出現的數。 【輸入格式】 第一行一個整數 T&#xff0c;表示數據組數。 對于每組數據&#xff0c;第一行一個整數 n。第二行 n 個數…

STM32固件升級設計——SPIFLASH模擬U盤升級固件

目錄 概述 一、功能描述 1、BootLoader部分&#xff1a; 2、APP部分&#xff1a; 二、BootLoader程序制作 1、分區定義 2、 主函數 3、配置USB 4、配置fatfs文件系統 5、程序跳轉 三、APP程序制作 四、工程配置&#xff08;默認KEIL5&#xff09; 五、運行測試 六…

解鎖阿里云日志服務SLS:云時代的日志管理利器

引言&#xff1a;開啟日志管理新篇 在云計算時代&#xff0c;數據如同企業的血液&#xff0c;源源不斷地產生并流動。從用戶的每一次點擊&#xff0c;到系統后臺的每一個操作&#xff0c;數據都在記錄著企業運營的軌跡。而在這些海量的數據中&#xff0c;日志數據占據著至關重…

Keye-VL-8B-Preview:由快手 Kwai Keye 團隊精心打造的尖端多模態大語言模型

&#x1f525; News 2025.06.26 &#x1f31f; 我們非常自豪地推出Kwai Keye-VL&#xff0c;這是快手Kwai Keye團隊精心打造的前沿多模態大語言模型。作為快手先進技術生態中的核心AI產品&#xff0c;Keye在視頻理解、視覺感知和推理任務方面表現卓越&#xff0c;樹立了新的性…

Web前端之JavaScript實現圖片圓環、圓環元素根據角度指向圓心、translate、rotate

MENU 前言效果HtmlStyleJavaScript 前言 代碼段創建了一個由6個WiFi圖標組成的圓形排列&#xff0c;每個圖標均勻分布在圓周上。 效果 Html 代碼 <div class"ring"><div class"item"><img class"img" src"../image/icon/W…

1 Studying《Computer Vision: Algorithms and Applications 2nd Edition》11-15

目錄 Chapter 11 Structure from motion and SLAM 11.1 幾何內稟校準 11.2 姿態估計 11.3 從運動中獲得的雙幀結構 11.4 從運動中提取多幀結構 11.5 同步定位與建圖&#xff08;SLAM&#xff09; 11.6 額外閱讀 Chapter 12 Depth estimation 12.1 極點幾何 12.2 稀疏…

phpstudy 可以按照mysql 數據庫

phpstudy 可以按照mysql 數據庫 PHPStudy&#xff08;小皮面板&#xff09;是一款專為開發者設計的集成環境工具&#xff0c;涵蓋服務器配置、開發環境搭建、網站部署等多項功能。以下是其核心用途及優勢的詳細解析&#xff1a; 一、開發環境快速搭建 一站式集成環境集成Apa…

Python搭建HTTP服務,如何用內網穿透快速遠程訪問?

Python的內置HTTP服務模塊是開發者工具箱中的瑞士軍刀&#xff0c;只需一行命令即可啟動一個功能完備的Web服務器。無論是前端工程師調試頁面、數據科學家共享Jupyter Notebook&#xff0c;還是后端開發者快速驗證API原型&#xff0c;Python HTTP服務都能以零配置的方式滿足需求…

撥號音識別系統的設計與實現

撥號音識別系統的設計與實現 摘要 本文設計并實現了一個完整的撥號音識別系統&#xff0c;該系統能夠自動識別電話號碼中的數字。系統基于雙音多頻(DTMF)技術原理&#xff0c;使用MATLAB開發&#xff0c;包含GUI界面展示處理過程和結果。系統支持從麥克風實時錄音或加載音頻文…

數據結構-樹詳解

樹簡介 樹存儲和組織具有層級結構的數據&#xff08;例&#xff1a;公司職級&#xff09;&#xff0c;就是一顆倒立生長的樹。 屬性&#xff1a; 遞歸n個節點有n-1個連接節點x的深度&#xff1a;節點x到根節點的最長路徑節點x的高度&#xff1a;節點x到葉子節點的最長路徑 …

【安卓Sensor框架-2】應用注冊Sensor 流程

注冊傳感器的核心流程為如下&#xff1a;應用層調用 SensorManager注冊傳感器&#xff0c;framework層創建SensorEventQueue對象&#xff08;事件隊列&#xff09;&#xff0c;通過JNI調用Native方法nativeEnableSensor()&#xff1b;SensorService服務端createEventQueue()創建…

新版本沒有docker-desktop-data分發 | docker desktop 鏡像遷移

在新版本的docker desktop中&#xff08;如4.42版本&#xff09;&#xff0c;鏡像遷移只需要更改路徑即可。如下&#xff1a; 打開docker desktop的設置&#xff08;圖1&#xff09;&#xff0c;將圖2的原來的地址C:\Users\用戶\AppData\Local\Docker\wsl修改為你想要的空文件…