news 2026/6/23 23:05:28

Linux C/C++ 学习日记(49):线程池

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux C/C++ 学习日记(49):线程池

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、线程池是什么?

  • 进程是操作系统进行资源分配的基本单位,线程是操作系统进行CPU调度的基本单位。
  • 线程池是维持管理一定数量线程的池式结构:
  • 维持就是复用、
  • 管理就是控制线程工作和休眠,
  • 一定数量是因为CPU核心数有限(线程池过多会增加CPU切换)

总的来说:线程池(Thread Pool)是一种预先创建一组可复用线程的资源管理机制,核心是维护一个线程集合,将任务提交到线程池后,由池中的空闲线程执行,避免频繁创建 / 销毁线程的开销,同时统一管理线程的生命周期

二、线程池解决的问题

异步执行耗时任务(不过度占用核心线程),充分利用多核。

耗时:

  • 耗时等待(io):如落盘操作、向数据库请求数据
  • 耗时计算:如加解密运算等

三、线程池是如何实现的呢?

在 C++ 中

可基于以下同步原语实现

  • std::thread:线程
  • std::mutex:互斥锁
  • std::condition_variable:条件变量

也可使用第三方库(如 Boost.ThreadPool)。

实现模板

  1. 生产消费模型:任务队列
  2. 生产者push任务到队列,唤醒一个休眠线程
  3. 消费者从任务队列里面取任务去执行
  4. 任务队列里面没有任务的话消费者会陷入休眠(线程阻塞)

问1:为什么采用队列?

  • 职责:生产者对应着一个口、消费者对应着另一个口
  • 队列的插入和读取O(1) 、 锁占用的时间短 、 锁的使用变得灵活

问2:线程池中的线程数量如何确定

  • cpu密集型:
    经验值:cpu核心数个线程数量。
    最优值:经验值+1、+2比对性能得到
  • io密集型:
    (线程等待时间+cpu运算时间)*cpu核心数/cpu运算时间
    经验值:2*cpu核心数个线程数量(一半等待、一半执行)。
    最优值:经验值+1、+2 比对性能得到
  • 性能测试(吞吐量):每秒执行的任务数量

问3:线程池的设计关键?

关键在于队列的设计和线程数量的管理。

1. 队列的设计

队列设计维度对应的线程池类型核心影响
队列边界(有界 / 无界)有界任务队列线程池 / 无界队列池决定任务积压时的行为(阻塞 / 拒绝 / 溢出)
队列排序规则普通 FIFO 池 / 优先级线程池决定任务执行的优先级(如 VIP 订单优先)
队列时间属性普通池 / 定时 / 延迟线程池决定任务是否支持延迟 / 周期性执行
队列数据结构普通队列池 / 批量任务池决定是否支持任务批量提取 / 执行
队列的数量生产者一个队列、消费者一个队列通过一次队列的交换减少了锁的竞争

tips:FIFO (First In, First Out) : 先进先出

举个典型例子:

  • 「优先级线程池」和「普通固定线程池」的核心差异,就是队列从std::queue(FIFO)换成了std::priority_queue(按优先级排序),线程数管理(固定)完全相同;
  • 「定时线程池」的核心改造也是队列 —— 将普通队列替换为「按执行时间排序的优先级队列」,线程会定时检查队列头部任务是否到执行时间;
  • 「有界任务队列线程池」和「无界固定线程池」的唯一差异,就是队列设置了最大容量,队列满时提交任务的线程会阻塞 / 拒绝任务。

2. 线程数量的管理

基础线程池类型核心区分依据(线程数量管理)
固定线程池线程数初始化后固定,永不增删
缓存线程池线程数动态增减(无核心线程,空闲超时销毁)
单线程池线程数固定为 1,任务串行执行

四、代码实现:

关键知识点

1. lock_guard与unique_lock

  • lock_guard 无法手动释放锁
  • unique_lock 可以手动释放锁


2. std::condition_variable not_empty_;

  • not_empty_.wait(lock, condi_func):释放锁,然后休眠,等待唤醒判断条件是否满足 (满足则获取锁,否则继续休眠)
  • not_empty_.notify_one(): 唤醒wait中的一个线程
  • not_empty_.notify_all() :唤醒wait中的所有线程,常用于销毁线程池

核心代码:

1. 固定线程池

threadpool.h
#pragma once #include <thread> #include <functional> #include <vector> // #include "blockingqueue.h" //避免循环依赖(头文件扩散)!!!!!,只在源文件中引用 // 前置声明 // blockingqueue 仅仅只能用作指针或引用 template <typename T> class BlockingQueue; class ThreadPool { public: // 初始化线程池 explicit ThreadPool(int threads_num); // 停止线程池 ~ThreadPool(); // 发布任务到线程池 void Post(std::function<void()> task); private: void Worker(); std::unique_ptr<BlockingQueue<std::function<void()>>> task_queue_; // 拷贝构造会报错 --> 防止外部调用拷贝构造 std::vector<std::thread> workers_; };
threadpool.cc
#include "blockingqueue.h" #include <memory> #include "threadpool.h" ThreadPool::ThreadPool(int threads_num) { task_queue_ = std::make_unique<BlockingQueue<std::function<void()>>>(); for (size_t i = 0; i < threads_num; ++i) { workers_.emplace_back([this] {Worker();}); } } // 停止线程池 ThreadPool::~ThreadPool() { task_queue_->Cancel(); for(auto &worker : workers_) { if (worker.joinable()) worker.join(); } } void ThreadPool::Post(std::function<void()> task) { task_queue_->Push(task); } void ThreadPool::Worker() { while (true) { std::function<void()> task; if (!task_queue_->Pop(task)) { break; } task(); } }

2. 队列的设计

blockingqueue.h

2.1. 单队列

生产者和消费者共用一个队列,意味着锁的竞争频繁

#pragma once #include <condition_variable> #include <functional> #include <queue> #include <mutex> #include <thread> template <typename T> class BlockingQueue { public: BlockingQueue(bool nonblock = false) : nonblock_(nonblock) { } // 入队操作 void Push(const T &value) { std::lock_guard<std::mutex> lock(mutex_); queue_.push(value); not_empty_.notify_one(); // 唤醒 not_empty_.wait(lock, [this]{ return !queue_.empty() || nonblock_; });处的worker } // 正常 pop 弹出元素 // 异常 pop 没有弹出元素 bool Pop(T &value) { std::unique_lock<std::mutex> lock(mutex_); // 1. mutex_.unlock() // 2. queue_empty && !nonblock 线程在 wait 中阻塞 // notify_one notify_all 唤醒线程 // 3. 假设满足条件 mutex_.lock() // 4. 不满足条件 回到 2 not_empty_.wait(lock, [this]{ return !queue_.empty() || nonblock_; }); // 线程休眠 if (queue_.empty()) return false; value = queue_.front(); queue_.pop(); return true; } // 解除阻塞在当前队列的线程 void Cancel() { std::lock_guard<std::mutex> lock(mutex_); nonblock_ = true; not_empty_.notify_all(); } private: bool nonblock_; std::queue<T> queue_; std::mutex mutex_; std::condition_variable not_empty_; };
2.2 双队列

生产者一个队列、消费者一个队列

#pragma once #include <condition_variable> #include <functional> #include <queue> #include <mutex> #include <thread> template <typename T> class BlockingQueuePro { public: BlockingQueuePro(bool nonblock = false) : nonblock_(nonblock) {} void Push(const T &value) { std::lock_guard<std::mutex> lock(prod_mutex_); prod_queue_.push(value); not_empty_.notify_one(); } bool Pop(T &value) { std::unique_lock<std::mutex> lock(cons_mutex_); if (cons_queue_.empty() && SwapQueue_() == 0) { return false; } value = cons_queue_.front(); cons_queue_.pop(); return true; } void Cancel() { std::lock_guard<std::mutex> lock(prod_mutex_); nonblock_ = true; not_empty_.notify_all(); } private: int SwapQueue_() { std::unique_lock<std::mutex> lock(prod_mutex_); not_empty_.wait(lock, [this] {return !prod_queue_.empty() || nonblock_; }); std::swap(prod_queue_, cons_queue_); return cons_queue_.size(); } bool nonblock_; std::queue<T> prod_queue_; std::queue<T> cons_queue_; std::mutex prod_mutex_; std::mutex cons_mutex_; std::condition_variable not_empty_; };
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 19:32:13

EmotiVoice语音害羞感模拟增添人际互动趣味

EmotiVoice&#xff1a;让语音“脸红”的情感合成技术如何重塑人机互动 你有没有想过&#xff0c;有一天你的语音助手在回答问题时会微微结巴、语速变慢&#xff0c;仿佛真的在“害羞”&#xff1f;这听起来像是科幻电影的桥段&#xff0c;但随着 EmotiVoice 这类高表现力语音合…

作者头像 李华
网站建设 2026/6/23 19:35:38

终极免费方案:李跳跳自定义规则一键告别所有弹窗广告

终极免费方案&#xff1a;李跳跳自定义规则一键告别所有弹窗广告 【免费下载链接】LiTiaoTiao_Custom_Rules 李跳跳自定义规则 项目地址: https://gitcode.com/gh_mirrors/li/LiTiaoTiao_Custom_Rules 你是否曾被手机上无处不在的弹窗广告折磨得苦不堪言&#xff1f;每次…

作者头像 李华
网站建设 2026/6/23 1:34:17

Linux系统编程:进程间通信

目录 一、进程间通信的背景 进程间通信方式 进程间通信目的&#xff08;为什么要进程间通信&#xff09; 二、管道 管道的特点 匿名管道 命名管道 匿名管道与命名管道的区别 三、System V共享内存 1.shmget函数 2.shmctl函数 3.shmat函数和shmdt函数 借助管道实现…

作者头像 李华
网站建设 2026/6/23 21:33:12

Linux系统编程:动静态库的操作

一、动静态库的基本介绍 在Linux操作系统中静态库文件是以 .a 作为后缀的&#xff0c;动态库以 .so 作为后缀。 静态库(.a)&#xff1a;静态库的代码是在程序编译链接的时候就被链接到可执行文件当中的&#xff0c;在程序运行的时候就不需要静态库代码了。动态库(.so)&#xff…

作者头像 李华
网站建设 2026/6/22 14:29:05

终极轻量化AI模型部署:完整快速配置指南

终极轻量化AI模型部署&#xff1a;完整快速配置指南 【免费下载链接】FastChat An open platform for training, serving, and evaluating large language models. Release repo for Vicuna and Chatbot Arena. 项目地址: https://gitcode.com/GitHub_Trending/fa/FastChat …

作者头像 李华
网站建设 2026/6/23 16:57:29

嵌入式分层架构藏着哪些秘密?

一、什么是嵌入式分层架构? 比喻:盖楼房 想象你要盖一栋楼: 地基层 = 硬件(芯片、电路、传感器) 结构层 = 驱动和硬件抽象层(柱子和梁) 功能层 = 中间件和操作系统(房间隔断和管道) 装修层 = 应用程序(墙面装饰和家具) 每一层都建立在下一层之上,且只与相邻层…

作者头像 李华