【C++】線程池實現

目錄

  • 一、線程池簡介
    • 線程池的核心組件
    • 實現步驟
  • 二、C++11實現線程池
    • 源碼
  • 三、線程池源碼解析
    • 1. 成員變量
    • 2. 構造函數
      • 2.1 線程初始化
      • 2.2 工作線程邏輯
    • 3. 任務提交(enqueue方法)
      • 3.1 方法簽名
      • 3.2 任務封裝
      • 3.3 任務入隊
    • 4. 析構函數
      • 4.1 停機控制
    • 5. 關鍵技術點解析
      • 5.1 完美轉發實現
      • 5.2 異常傳播機制
      • 5.3 內存管理模型
  • 四、 性能特征分析
  • 五、 擴展優化方向
  • 六、 典型問題排查指南
  • 七、 測試用例
    • 如果這篇文章對你有所幫助,渴望獲得你的一個點贊!

一、線程池簡介

線程池是一種并發編程技術,通過預先創建一組線程并復用它們來執行多個任務,避免了頻繁創建和銷毀線程的開銷。它特別適合處理大量短生命周期任務的場景(如服務器請求、并行計算)。

線程池的核心組件

1. 任務隊列(Task Queue)
存儲待執行的任務(通常是函數對象或可調用對象)。

2. 工作線程(Worker Threads)
一組預先創建的線程,不斷從隊列中取出任務并執行。

3. 同步機制
互斥鎖(Mutex):保護任務隊列的線程安全訪問。
條件變量(Condition Variable):通知線程任務到達或線程池終止。

實現步驟

1. 初始化線程池
創建固定數量的線程,每個線程循環等待任務。

2. 提交任務
將任務包裝成函數對象,加入任務隊列。

3. 任務執行
工作線程從隊列中取出任務并執行。

4. 終止線程池
發送停止信號,等待所有線程完成當前任務后退出。

二、C++11實現線程池

源碼

#include <vector>
#include <queue>
#include <future>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <stdexcept>class ThreadPool 
{
public://構造函數:根據輸入的線程數(默認硬件并發數)創建工作線程。//每個工作線程執行一個循環,不斷從任務隊列中取出并執行任務。//explicit關鍵字防止隱式類型轉換explicit ThreadPool(size_t threads = std::thread::hardware_concurrency()): stop(false) {if (threads == 0) {threads = 1;}for (size_t i = 0; i < threads; ++i) {workers.emplace_back([this] {for (;;) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queue_mutex);//等待條件:線程通過條件變量等待任務到來或停止信號。(CPU使用率:休眠時接近0%,僅在任務到來時喚醒)//lambda表達式作為謂詞,當條件(停止信號為true 或 任務隊列非空)為真時,才會解除阻塞。this->condition.wait(lock, [this] {return (this->stop || !this->tasks.empty());});/* 傳統忙等待:while (!(stop || !tasks.empty())) {} // 空循環消耗CPU */if (this->stop && this->tasks.empty()){//如果線程池需要終止且任務隊列為空則直接returnreturn;}//任務提取:從隊列中取出任務并執行,使用std::move避免拷貝開銷。task = std::move(this->tasks.front());this->tasks.pop();}//執行任務task();}});}}//任務提交(enqueue方法)template<class F, class... Args>auto enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;//任務封裝:使用std::packaged_task包裝用戶任務,支持異步返回結果。//智能指針管理:shared_ptr確保任務對象的生命周期延續至執行完畢。//完美轉發:通過std::forward保持參數的左值/右值特性。auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if (stop){throw std::runtime_error("enqueue on stopped ThreadPool");}  tasks.emplace([task]() { (*task)(); });/* push傳入的對象需要事先構造好,再復制過去插入容器中;而emplace則可以自己使用構造函數所需的參數構造出對象,并直接插入容器中。emplace相比于push省去了復制的步驟,則使用emplace會更加節省內存。*/}condition.notify_one();return res;}~ThreadPool() {//設置stop標志,喚醒所有線程,等待任務隊列清空。{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (std::thread& worker : workers){worker.join();}}private:std::vector<std::thread> workers;        //存儲工作線程對象std::queue<std::function<void()>> tasks; //任務隊列,存儲待執行的任務std::mutex queue_mutex;                  //保護任務隊列的互斥鎖std::condition_variable condition;       //線程間同步的條件變量bool stop;                               //線程池是否停止標志
};

三、線程池源碼解析

1. 成員變量

std::vector<std::thread> workers;        // 工作線程容器
std::queue<std::function<void()>> tasks; // 任務隊列
std::mutex queue_mutex;                  // 隊列互斥鎖
std::condition_variable condition;       // 條件變量
bool stop;                               // 停機標志

設計要點:

  • 采用生產者-消費者模式,任務隊列作為共享資源

  • 組合使用mutex+condition_variable實現線程同步

  • vector存儲線程對象便于統一管理生命周期


2. 構造函數

2.1 線程初始化

explicit ThreadPool(size_t threads = std::thread::hardware_concurrency()): stop(false)
{if (threads == 0) {threads = 1;}for (size_t i = 0; i < threads; ++i) {workers.emplace_back([this] { /* 工作線程邏輯 */ });}
}

設計要點:

  • explicit防止隱式類型轉換(如ThreadPool pool = 4;

  • 默認使用硬件并發線程數(通過hardware_concurrency()

  • 最少創建1個線程避免空池

  • 使用emplace_back直接構造線程對象


2.2 工作線程邏輯

for (;;)
{std::function<void()> task;{std::unique_lock<std::mutex> lock(queue_mutex);condition.wait(lock, [this] {return stop || !tasks.empty();});if (stop && tasks.empty()) {return; }task = std::move(tasks.front());tasks.pop();}task();
}

核心機制:

  • unique_lock配合條件變量實現自動鎖管理

  • 雙重狀態檢查(停機標志+隊列非空)

  • 任務提取使用移動語義避免拷貝

  • 任務執行在鎖作用域外進行


3. 任務提交(enqueue方法)

3.1 方法簽名

template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)-> std::future<typename std::result_of<F(Args...)>::type>

類型推導:

  • 使用尾置返回類型聲明
  • std::result_of推導可調用對象的返回類型
  • 完美轉發參數(F&&+Args&&...

3.2 任務封裝

auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));

封裝策略:

  • packaged_task包裝任務用于異步獲取結果
  • shared_ptr管理任務對象生命周期
  • std::bind綁定參數(注意C++11的參數轉發限制)

3.3 任務入隊

tasks.emplace([task]() { (*task)(); });

優化點:

  • 使用emplace直接構造隊列元素
  • Lambda捕獲shared_ptr保持任務有效性
  • 顯式解引用執行packaged_task

4. 析構函數

4.1 停機控制

~ThreadPool() 
{{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (auto& worker : workers){worker.join();}  
}

停機協議:

  1. 設置停機標志原子操作
  2. 廣播喚醒所有等待線程
  3. 等待所有工作線程退出

5. 關鍵技術點解析

5.1 完美轉發實現

std::bind(std::forward<F>(f), std::forward<Args>(args)...)
  • 保持參數的左右值特性
  • 支持移動語義參數的傳遞
  • C++11的限制:無法完美轉發所有參數類型

5.2 異常傳播機制

  • 任務異常通過future對象傳播
  • packaged_task自動捕獲異常
  • 用戶通過future.get()獲取異常

5.3 內存管理模型

         [任務提交者]|v[packaged_task] <---- shared_ptr ---- [任務隊列]|v[future]
  • 三重生命周期保障:
    1. 提交者持有future
    2. 隊列持有任務包裝器
    3. 工作線程執行任務

四、 性能特征分析

1. 時間復雜度

操作時間復雜度
任務提交(enqueue)O(1)(加鎖開銷)
任務提取O(1)
線程喚醒取決于系統調度

2. 空間復雜度

組件空間占用
線程棧每線程MB級
任務隊列與任務數成正比
同步原語固定大小

五、 擴展優化方向

1. 任務竊取(Work Stealing)

  • 實現多個任務隊列
  • 空閑線程從其他隊列竊取任務

2. 動態線程池

void adjust_workers(size_t new_size) 
{if (new_size > workers.size()) {// 擴容邏輯} else {// 縮容邏輯}
}

3. 優先級隊列

using Task = std::pair<int, std::function<void()>>; // 優先級+任務std::priority_queue<Task> tasks;

4. 無鎖隊列

moodycamel::ConcurrentQueue<std::function<void()>> tasks;

六、 典型問題排查指南

現象可能原因解決方案
任務未執行線程池提前析構延長線程池生命周期
future.get()永久阻塞任務未提交/異常未處理檢查任務提交路徑
CPU利用率100%忙等待或鎖競爭優化任務粒度/使用無鎖結構
內存持續增長任務對象未正確釋放檢查智能指針使用

該實現完整展現了現代C++線程池的核心設計范式,開發者可根據具體需求在此基礎進行功能擴展和性能優化。理解這個代碼結構是掌握更高級并發模式的基礎。

七、 測試用例

使用實例(C++11兼容):

#include <iostream>int main() 
{ThreadPool pool(4);// 提交普通函數auto future1 = pool.enqueue([](int a, int b) {return a + b;}, 2, 3);// 提交成員函數struct Calculator {int multiply(int a, int b) { return a * b; }} calc;auto future2 = pool.enqueue(std::bind(&Calculator::multiply, &calc, std::placeholders::_1, std::placeholders::_2), 4, 5);// 異常處理示例auto future3 = pool.enqueue([]() -> int {throw std::runtime_error("example error");return 1;});std::cout << "2+3=" << future1.get() << std::endl;std::cout << "4*5=" << future2.get() << std::endl;try {future3.get();} catch(const std::exception& e){std::cout << "Caught exception: " << e.what() << std::endl;}return 0;
}

如果這篇文章對你有所幫助,渴望獲得你的一個點贊!

在這里插入圖片描述

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

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

相關文章

深入理解 C# 與.NET 框架

.NET學習資料 .NET學習資料 .NET學習資料 一、引言 在現代軟件開發領域&#xff0c;C# 與.NET 框架是構建 Windows、Web、移動及云應用的強大工具。C# 作為一種面向對象的編程語言&#xff0c;而.NET 框架則是一個綜合性的開發平臺&#xff0c;它們緊密結合&#xff0c;為開…

雷電等基于VirtualBox的Android模擬器映射串口和測試CSerialPort串口功能

雷電等基于VirtualBox的Android模擬器映射串口和測試CSerialPort串口功能 1. 修改VirtualBox配置文件映射串口 模擬器配置文件vms/leidian0/leidian.vbox。 在UART標簽下增加(修改完成后需要將leidian.vbox修改為只讀) <Port slot"1" enabled"true"…

【Linux系統】SIGCHLD 信號(選學了解)

SIGCHLD 信號 使用wait和waitpid函數可以有效地清理僵尸進程。父進程可以選擇阻塞等待&#xff0c;直到子進程結束&#xff1b;或者采用非阻塞的方式&#xff0c;通過輪詢檢查是否有子進程需要被回收。 然而&#xff0c;無論是選擇阻塞等待還是非阻塞的輪詢方式&#xff0c;父…

【R語言】獲取數據

R語言自帶2種數據存儲格式&#xff1a;*.RData和*.rds。 這兩者的區別是&#xff1a;前者既可以存儲數據&#xff0c;也可以存儲當前工作空間中的所有變量&#xff0c;屬于非標準化存儲&#xff1b;后者僅用于存儲單個R對象&#xff0c;且存儲時可以創建標準化檔案&#xff0c…

Vim的基礎命令

移動光標 H(左) J(上) K(下) L(右) $ 表示移動到光標所在行的行尾&#xff0c; ^ 表示移動到光標所在行的行首的第一個非空白字符。 0 表示移動到光標所在行的行首。 W 光標向前跳轉一個單詞 w光標向前跳轉一個單詞 B光標向后跳轉一個單詞 b光標向后跳轉一個單詞 G 移動光標到…

11. 9 構建生產級聊天對話記憶系統:從架構設計到性能優化的全鏈路指南

構建生產級聊天對話記憶系統:從架構設計到性能優化的全鏈路指南 關鍵詞: 聊天對話記憶系統、多用戶會話管理、LangChain生產部署、Redis記憶存儲、高并發對話系統 一、服務級聊天記憶系統核心需求 多用戶隔離:支持同時處理數千個獨立對話持久化存儲:對話歷史不因服務重啟丟…

Block Blaster Online:免費解謎游戲的樂趣

Block Blaster Online 是一款免費的在線解謎游戲&#xff0c;它將挑戰你的思維和反應能力&#xff01;在這里&#xff0c;你可以匹配五彩繽紛的方塊&#xff0c;創造出令人驚嘆的組合&#xff0c;享受無盡的解謎樂趣。無需安裝&#xff0c;點擊即可開始&#xff0c;加入全球數百…

Guided Decoding (借助FSM,有限狀態自動機)

VLLM對結構化輸出的支持&#xff1a; vllm/docs/source/features/structured_outputs.md at main vllm-project/vllm GitHub VLLM對tool call的支持&#xff1a; vllm/docs/source/features/tool_calling.md at main vllm-project/vllm GitHub 以上指定輸出格式&#xf…

IFeatureWorkspace.CreateFeatureClass(),報錯對COM組件的調用返回了錯誤 HRESULT E_FAIL

1、問題描述&#xff1a;在AE開發中&#xff0c;新增一個空的shpfile文件的時候&#xff0c;報錯&#xff0c;如下圖&#xff1a; 2、原因分析&#xff1a;產生此問題的原因是未設置默認字段的默認參數&#xff0c;特別是未設置IGeometryDef 參數。 3、解決方案&#xff1a;在…

算法題(48):反轉鏈表

審題&#xff1a; 需要我們將鏈表反轉并返回頭結點地址 思路&#xff1a; 一般在面試中&#xff0c;涉及鏈表的題會主要考察鏈表的指向改變&#xff0c;所以一般不會允許我們改變節點val值。 這里是單向鏈表&#xff0c;如果要把指向反過來則需要同時知道前中后三個節點&#x…

內存的介紹

1、程序運行為什么需要內存 1.1、計算機程序運行的目的 (1)程序的目的是為了去運行&#xff0c;程序運行是為了得到一定的結果。 (2)計算機程序 代碼 數據。計算機程序運行完得到一個結果&#xff0c;就是說 代碼 數據 (經過運行后) 結果。 (3)從宏觀上來理解&#xff…

【NLP百面百過】大模型算法面試高頻面題(全面整理 ???)

目錄 一、大模型面試指南 重點面題精講 【LLM面題精講 - RAG系統面】 查看答案 【LLM面題精講 - 實體識別面】 查看答案 【LLM面題精講 - 文本分類面】 查看答案 【LLM面題精講 - 分布式訓練面】 查看答案 【LLM面題精講 - 大模型微調面】 查看答案 【LLM面題精講 - 大…

Java 大視界 -- Java 大數據在智能醫療影像診斷中的應用(72)

??親愛的朋友們,熱烈歡迎來到 青云交的博客!能與諸位在此相逢,我倍感榮幸。在這飛速更迭的時代,我們都渴望一方心靈凈土,而 我的博客 正是這樣溫暖的所在。這里為你呈上趣味與實用兼具的知識,也期待你毫無保留地分享獨特見解,愿我們于此攜手成長,共赴新程!?? 一、…

基于 docker 的mysql 5.7 主主集群搭建

創建掛載目錄和配置文件 主節點1 mkdir -p /mysql_master_1/mysql/log mkdir -p /mysql_master_1/mysql/data mkdir -p /mysql_master_1/mysql/conf vim /mysql_master_1/mysql/conf/my.cnf[mysqld] datadir/var/lib/mysql #MySQL 數據庫文件存放路徑 server_id 1 #指定數據…

list容器(詳解)

list的介紹及使用&#xff08;了解&#xff0c;后邊細講&#xff09; 1.1 list的介紹&#xff08;雙向循環鏈表&#xff09; https://cplusplus.com/reference/list/list/?kwlist&#xff08;list文檔介紹&#xff09; 1. list是可以在常數范圍內在任意位置進行插入和刪除的序…

MapReduce分區

目錄 1. MapReduce分區1.1 哈希分區1.2 自定義分區 2. 成績分組2.1 Map2.2 Partition2.3 Reduce 3. 代碼和結果3.1 pom.xml中依賴配置3.2 工具類util3.3 GroupScores3.4 結果 參考 本文引用的Apache Hadoop源代碼基于Apache許可證 2.0&#xff0c;詳情請參閱 Apache許可證2.0。…

kamailio-ACC_JSON模塊詳解【后端語言go】

要確認 ACC_JSON 模塊是否已經成功將計費信息推送到消息隊列&#xff08;MQueue&#xff09;&#xff0c;以及如何從隊列中取值&#xff0c;可以按照以下步驟進行操作&#xff1a; 1. 確認 ACC_JSON 已推送到隊列 1.1 配置 ACC_JSON 確保 ACC_JSON 模塊已正確配置并啟用。以下…

網件r7000刷回原廠固件合集測評

《網件R7000路由器刷回原廠固件詳解》 網件R7000是一款備受贊譽的高性能無線路由器&#xff0c;其強大的性能和可定制性吸引了許多高級用戶。然而&#xff0c;有時候用戶可能會嘗試第三方固件以提升功能或優化網絡性能&#xff0c;但這也可能導致一些問題&#xff0c;如系統不…

【C++STL標準模板庫】二、STL三大組件

文章目錄 1、容器2、算法3、迭代器 二、STL三大組件 1、容器 容器&#xff0c;置物之所也。 研究數據的特定排列方式&#xff0c;以利于搜索或排序或其他特殊目的&#xff0c;這一門學科我們稱為數據結構。大學信息類相關專業里面&#xff0c;與編程最有直接關系的學科&…

基于 Java 開發的 MongoDB 企業級應用全解析

基于Java的MongoDB企業級應用開發實戰 目錄 背景與歷史MongoDB的核心功能與特性企業級業務場景分析MongoDB的優缺點剖析開發環境搭建 5.1 JDK安裝與配置5.2 MongoDB安裝與集群配置5.3 開發工具選型 Java與MongoDB集成實戰 6.1 項目依賴與驅動選擇6.2 連接池與客戶端配置6.3…