【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.mzph.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…