当前位置: 首页 > news >正文

【C++】线程池

C++ 线程池介绍

  1. 什么是线程池?
    线程池(Thread Pool) 是一种并发编程模型,用于管理和复用多个线程,避免频繁创建/销毁线程的开销。它通过预创建一组线程,并将任务提交到队列中,由空闲线程自动执行,从而提升多线程程序的性能和资源利用率。

  1. 为什么需要线程池?
  • 降低开销:线程创建/销毁成本高(涉及系统调用、内存分配)。
  • 任务调度优化:避免线程竞争CPU导致的上下文切换损耗。
  • 资源管控:限制并发线程数量,防止系统过载。
  • 简化编程:将任务提交与执行逻辑解耦。

适用场景
高并发服务器、批量数据处理、异步任务执行(如日志写入)、GUI后台计算等。

单队列线程池

简单高效,推荐使用!!!

实现

以下是基于C++11/17标准库的线程池简化实现

#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>class ThreadPool {
public:explicit ThreadPool(size_t num_threads = std::thread::hardware_concurrency()) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back([this] {while (true) {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(); // 执行任务}});}}template<typename F, typename... Args>auto enqueue(F&& f, Args&&... args) -> std::future<decltype(f(args...))> {using return_type = decltype(f(args...));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)(); });}condition.notify_one(); // 唤醒一个线程return res;}~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (std::thread& worker : workers) {if (worker.joinable()) worker.join();}}private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop = false;
};

线程池核心组件

组件作用
任务队列存储待执行的任务(std::queue<std::function<void()>>
工作线程组预创建的线程集合,循环从队列中取任务执行(std::vector<std::thread>
互斥锁保护任务队列的线程安全访问(std::mutex
条件变量协调线程的休眠与唤醒(std::condition_variable
停止标志安全终止所有线程(bool stop

使用示例

#include <iostream>int main() {ThreadPool pool(4); // 创建4个线程的线程池// 提交多个任务std::vector<std::future<int>> results;for (int i = 0; i < 8; ++i) {results.emplace_back(pool.enqueue([i] {std::this_thread::sleep_for(std::chrono::seconds(1));return i * i;}));}// 获取结果for (auto&& result : results) {std::cout << result.get() << ' ';}std::cout << std::endl;return 0;
}

输出0 1 4 9 16 25 36 49(执行时间约2秒,4线程并行)


优化方向

  1. 动态扩缩容:根据任务负载动态调整线程数量。
  2. 任务优先级:支持优先级队列(如std::priority_queue)。
  3. 任务窃取:允许空闲线程从其他线程的任务队列窃取任务。
  4. 返回值处理:使用std::futurestd::packaged_task传递异步结果。
  5. 异常处理:捕获任务中的异常并传递到主线程。

注意事项

  • 线程安全:确保任务队列的访问通过锁保护。
  • 资源释放:析构时需等待所有任务完成,避免资源泄漏。
  • 死锁风险:避免在任务中阻塞等待其他任务的结果。
  • 性能调优:根据硬件核心数设置合理线程数(通常为CPU核心数 + 1)。

通过线程池,可以显著提升C++程序的并发性能,同时保持代码的简洁与可维护性。

双队列线程池

总体来说,不推荐!!!


线程池是否可以使用两个队列(空闲队列 & 工作队列)?

在传统线程池设计中,通常使用单个任务队列来管理待执行的任务,所有工作线程共享此队列,并通过条件变量协调任务的获取与执行。但根据实际需求,可以通过两个队列(空闲线程队列 + 工作线程队列)来优化管理,以下是详细分析:


对比

设计方式单任务队列双队列(空闲+工作)
核心思想所有线程共享一个任务队列,竞争获取任务显式区分空闲线程和工作线程,按需调度
线程状态管理隐式(通过条件变量等待)显式(记录线程是否空闲)
适用场景通用场景,简单高效需要动态扩缩容、优先级调度等复杂场景
实现复杂度简单较高(需维护线程状态和队列同步)

双队列设计的意义

  • 动态线程管理
    根据任务负载动态调整活跃线程数量,例如:
    • 当任务队列积压时,从空闲队列唤醒更多线程。
    • 当任务减少时,将空闲线程挂起或释放,减少资源占用。
  • 优先级调度
    允许为不同优先级的任务分配独立队列,结合线程状态实现更精细的调度。
  • 避免过度竞争
    将空闲线程与工作线程分离,减少锁争用(例如:空闲队列单独管理)。

双队列实现示例

以下是一个基于空闲队列和工作队列的线程池框架:

#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>class ThreadPool {
public:explicit ThreadPool(size_t max_threads) : max_threads(max_threads) {}void enqueue(std::function<void()> task) {{std::unique_lock<std::mutex> lock(task_mutex);tasks.push(std::move(task));}// 尝试唤醒空闲线程或创建新线程if (!idle_threads.empty()) {std::unique_lock<std::mutex> lock(idle_mutex);auto thread = idle_threads.front();idle_threads.pop();lock.unlock();thread->assign_task(); // 唤醒空闲线程} else if (active_threads < max_threads) {create_thread(); // 创建新线程}}private:size_t max_threads;size_t active_threads = 0;std::queue<std::function<void()>> tasks;std::mutex task_mutex;// 空闲线程队列(存储可复用的线程对象)std::queue<std::shared_ptr<WorkerThread>> idle_threads;std::mutex idle_mutex;class WorkerThread {public:void assign_task(std::function<void()> task) {{std::unique_lock<std::mutex> lock(mtx);current_task = std::move(task);has_task = true;}cv.notify_one();}void run() {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [&] { return has_task || terminate; });if (terminate) return;task = std::move(current_task);has_task = false;}task(); // 执行任务// 任务完成后,将自己放回空闲队列pool->return_to_idle(this);}}bool is_idle() const { return !has_task; }private:std::mutex mtx;std::condition_variable cv;std::function<void()> current_task;bool has_task = false;bool terminate = false;};void create_thread() {auto worker = std::make_shared<WorkerThread>();std::thread([worker] { worker->run(); }).detach();active_threads++;}void return_to_idle(WorkerThread* thread) {std::unique_lock<std::mutex> lock(idle_mutex);idle_threads.push(thread);}
};

双队列的优缺点

优点缺点
动态扩缩容:按需增加/减少活跃线程实现复杂度高(需管理线程生命周期)
减少锁竞争(空闲与工作线程分离)上下文切换可能增多(线程频繁唤醒/挂起)
支持更复杂的调度策略(如优先级)内存占用更高(需维护额外队列)

适用场景

  • 突发性高负载:任务量波动大时,动态调整线程数量。
  • 资源敏感环境:需严格控制线程数量(如嵌入式系统)。
  • 任务优先级调度:高优先级任务优先分配空闲线程。

总结

  • 单队列足够大多数场景:简单高效,推荐优先使用。
  • 双队列适合特殊需求:当需要动态扩缩容、优先级调度或减少锁竞争时,可考虑双队列设计,但需权衡实现复杂度与性能收益。

根据具体需求选择设计,避免过度工程化!

相关文章:

  • VASP 教程:VASP 结合 phonopy 计算硅的声子谱
  • UDP 报文结构与注意事项总结
  • 打造即插即用的企业级云原生平台——KubeSphere 4.1 扩展组件在生产环境的价值全解
  • Vue 生命周期钩子总结
  • 第9讲:坐标轴美学深度优化——刻度线、网格线与边框控制
  • 【更新】LLM Interview (2)
  • CMCC RAX3000M使用Tftpd刷写OpenWrt固件的救砖方法
  • 顶会idea:Mamba+CNN暴力涨点新突破!
  • 一种在使用Kaggle并遇上会话中断时强行保存数据的方法
  • 国标云台控制状态
  • C语言-指针(一)
  • Paramiko 完全指南
  • 2020南京区域赛vp
  • InnoDB对LRU算法的优化
  • LangChain入门(二)安装开发环境
  • MCP 模型上下文协议配置MCP Server开发实践
  • Spark知识总结
  • AI在Java中的场景面试题深度解析
  • c++之使用 libdl.so 和 <dlfcn.h> 实现动态链接
  • MySQL 的ANALYZE与 OPTIMIZE命令
  • 王毅出席金砖国家外长会晤
  • 四川落马厅官周海琦受审,1000多人接受警示教育
  • 李强主持召开国务院常务会议
  • 俄罗斯准备在没有先决条件的情况下与乌克兰进行谈判
  • 一年吸引30多万人次打卡,江苏这个渔村是怎么做到的?
  • 体育公益之约跨越山海,雪域高原果洛孕育足球梦