news 2026/5/6 12:47:33

线程池项目2

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线程池项目2

一、模块划分与职责

模块作用
Any类型擦除容器,用于存储任务返回的任意类型值,支持安全类型转换。
Semaphore基于mutex+condition_variable实现的计数信号量,用于线程间同步。
Task任务抽象基类,用户继承并实现run(),通过exec()触发执行并设置结果。
Result任务返回值载体,内部含信号量,用户调用get()阻塞等待结果到达。
Thread轻量线程封装,存储线程函数和自增ID,启动时detach分离。
ThreadPool核心管理类:任务队列、线程池、模式切换、动态扩缩容、生命周期控制。

二、核心机制详解

1. 生产者-消费者模型

  • 生产者:用户调用submitTask将任务shared_ptr<Task>放入任务队列。

  • 消费者:池内所有线程运行threadFunc,循环从队列中取任务执行。

  • 线程安全:任务队列由taskQueMtx_互斥锁保护,配合notEmpty_notFull_条件变量实现阻塞/通知。

2. 两种工作模式

  • MODE_FIXED(固定模式)
    线程数量在start()时确定,永不增减。任务队列满时提交者阻塞(最长1秒)。

  • MODE_CACHED(缓存模式)

    • 动态扩容:当taskSize_ > idleThreadSize_且当前线程数未达上限时,立即创建新线程。

    • 空闲回收:线程在无任务时使用wait_for超时等待,若空闲超过THREAD_MAX_IDLE_TIME(60秒)且当前线程数 > 初始线程数,则线程自我销毁(从threads_中删除并退出)。

3. 异步结果获取

  • 每个任务提交时返回一个Result对象,内部持有指向该任务的shared_ptr和一个信号量。

  • 任务执行完时,Task::exec()调用run()得到Any返回值,再通过Result::setVal存储并post信号量。

  • 用户线程调用Result::get()wait信号量,直到结果就绪。

4. 线程生命周期管理

  • 启动start(initThreadSize)创建Thread对象,每个对象启动时detach分离线程,线程函数绑定到ThreadPool::threadFunc

  • 正常退出:析构函数设置isPoolRunning_ = false,唤醒所有等待线程,每个线程检测到标志后从threads_中删除自身,最后通知exitCond_,主线程等待所有线程退出。

  • 缓存模式主动回收:空闲超时的线程直接return,同样会执行threads_.erase(threadid)并修改计数。

5. 任务队列的背压控制

  • 提交任务时,最多等待1秒直到队列有空间(notFull_.wait_for),否则返回无效ResultisValid_=false)。

  • 取出任务后,若队列仍非空,则通知其他线程继续取任务;同时通知notFull_,让可能阻塞的生产者继续提交。

三、设计亮点与权衡

  1. 类型擦除的返回值Any类通过多态 + 模板派生,避免了void*的不安全性,用户获取时需显式指定类型(cast_<T>()),若类型不匹配则抛异常。

  2. 信号量自实现:C++11 标准库无信号量,用mutex+condition_variable封装了一个简洁的Semaphore,用于Result中实现单次同步。

  3. 线程ID的自定义映射:每个Thread有自增整型ID,线程池用unordered_map<int, unique_ptr<Thread>>存储,线程函数通过传入的threadid参数在退出时删除自身条目。
    注意:这里依赖“线程函数参数就是 map 的 key”这一约定,且删除操作在持有锁时进行,避免了并发问题。

  4. 原子操作与锁的精细使用taskSize_curThreadSize_idleThreadSize_使用atomic_int减少锁竞争;但任务队列的操作仍必须加锁,以保证条件变量的正确性。

  5. 资源回收的同步:析构时使用exitCond_等待所有线程退出,避免detach后主线程退出导致进程 crash。每个线程退出前都会notify_all,确保主线程被唤醒。

  6. 可扩展性:用户只需继承Task并实现run(),即可定义任意业务逻辑,线程池只负责调度和结果传递。


问题一:如果把线程池的任务队列从 std::shared_ptr<Task> 改成裸指针 std::queue<Task*>会怎么样

void someFunction() { MyTask task; // 栈上的临时对象 pool.submitTask(&task); // 传入裸指针 } // 函数结束,task 被销毁
  • 线程池内部将&task存入std::queue<Task*>

  • someFunction执行完毕后,task对象生命周期结束,内存被回收(或栈空间被复用)。

  • 队列中只剩下一个指向已销毁对象的指针。

void someFunction() { auto sp = std::make_shared<MyTask>(); // 堆对象,引用计数 = 1 pool.submitTask(sp); // 传入 sp(左值,拷贝) } // someFunction 结束,sp 销毁,引用计数减 1(但队列中还有一份,对象不销毁)

在拷贝的时候计数器会加1,所以不用担心对象会销毁

问题二:为什么要自己封装一个Thread类呢

封装Thread类的根本原因是:给系统线程附加一个整数 ID,以便在线程池中高效地标识、管理和回收特定线程

具体来说,std::thread本身不提供直接可用的整数标识符,其get_id()返回的std::thread::id是一个不透明的结构体,不能直接作为unordered_map的键(需要特化hash),也无法方便地打印为整数用于日志或调试。

通过自定义Thread类,作者实现了:

  1. 自增整数 IDstatic int generateId_确保每个线程获得一个唯一的整数 ID,可以直接作为unordered_map<int, unique_ptr<Thread>>的键。

  2. ID 传递到线程函数:启动std::thread时,将整数 ID 作为参数传递给线程函数(threadFunc(int threadid)),这样线程函数就能知道自己是谁,从而在需要退出时安全地调用threads_.erase(threadid)删除自身。

  3. 封装detach行为:让线程池无需显式管理每个线程的join,简化生命周期控制。

如果不封装,直接用vector<std::thread>,线程函数就无法获得一个简单的整数索引来从容器中删除自己,管理起来会复杂且低效。因此,封装Thread是一种轻量级、实用的设计模式。

问题三:把线程函数写进Thread会带来的问题

如果Thread内部直接写死任务循环逻辑,那么Thread就必须知道任务队列、互斥锁、条件变量、线程池模式等细节。这意味着Thread无法脱离ThreadPool独立使用,也失去了复用性。

问题四:为什么要绑定

Thread类想要调用ThreadPool类的成员函数threadFunc,但成员函数必须通过某个ThreadPool对象来调用(需要this指针),而Thread的接口只接受一个参数为int的普通函数对象。通过std::bind(或 lambda)将this绑定进去,生成一个符合void(int)签名的新可调用对象,就能让Thread在启动时传入整数 ID,正确调用到对应线程池实例的成员函数。

如果不绑定,直接传递成员函数指针,类型不匹配,编译会失败。

给包装器类别起个别名

问题五:为什么分离线程

t是临时变量, 分离线程后,即使对象t被销毁, 线程也会独立在后台运行,直到它执行的函数(func_)返回

上边三行的写法相当于下面一行的写法

问题:怎么设计run函数的返回值,可以表示任意的类型

问题:如何设计Result机制呢

用c++知识,构建一个Any类型
基类指针指向派生类+模板

构建一个信号量类
互斥锁加条件变量

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/10 12:00:01

SmallThinker-3B-Preview部署详解:Windows系统本地化Docker部署指南

SmallThinker-3B-Preview部署详解&#xff1a;Windows系统本地化Docker部署指南 想在自己的Windows电脑上跑一个轻量级的AI模型&#xff0c;试试它的推理能力&#xff0c;但又觉得环境配置太麻烦&#xff1f;如果你也有这个想法&#xff0c;那今天这篇教程就是为你准备的。 S…

作者头像 李华
网站建设 2026/4/10 11:59:38

AI Coding 最佳实践

AI Coding 最佳实践Vibe Coding心态转变高效工作流提示词技巧质量控制与避坑黄金法则总结AI coding宏观摸底找一个抓手安全干预—— 严格控制 AI 的动作范围阶段四&#xff1a;防御性验证模型选择Vibe Coding Vibe Coding 是 AI Coding 发展到极致&#xff08;比如 Cursor Cl…

作者头像 李华
网站建设 2026/4/10 11:59:05

Sonic数字人效果展示:生成逼真说话视频,效果惊艳

Sonic数字人效果展示&#xff1a;生成逼真说话视频&#xff0c;效果惊艳 1. 数字人视频制作新范式 在内容创作领域&#xff0c;一个革命性的变化正在发生&#xff1a;只需一张照片和一段音频&#xff0c;就能让静态人物"活"起来&#xff0c;生成栩栩如生的说话视频…

作者头像 李华
网站建设 2026/4/10 11:58:47

不用装软件!电脑自带语音输入,Win+H 一键开启,打字速度翻倍

日常写文档、回消息、填表格时&#xff0c;长时间打字又累又慢&#xff0c;很多人不知道Windows 系统自带免费语音输入&#xff0c;不用额外下载 APP、不用付费会员&#xff0c;识别速度快、准确率高&#xff0c;办公效率直接翻倍。不管是笔记本还是台式机&#xff0c;只要有麦…

作者头像 李华
网站建设 2026/4/10 11:58:45

RVC变声器终极指南:10分钟训练AI音色模型的完整教程

RVC变声器终极指南&#xff1a;10分钟训练AI音色模型的完整教程 【免费下载链接】Retrieval-based-Voice-Conversion-WebUI Easily train a good VC model with voice data < 10 mins! 项目地址: https://gitcode.com/GitHub_Trending/re/Retrieval-based-Voice-Conversio…

作者头像 李华
网站建设 2026/4/10 11:58:34

Linux系统安装哔哩哔哩客户端的终极指南:从源码编译到高级功能配置

Linux系统安装哔哩哔哩客户端的终极指南&#xff1a;从源码编译到高级功能配置 【免费下载链接】bilibili-linux 基于哔哩哔哩官方客户端移植的Linux版本 支持漫游 项目地址: https://gitcode.com/gh_mirrors/bi/bilibili-linux 在Linux平台上享受完整的哔哩哔哩体验不再…

作者头像 李华