【Linux】线程池详解及基本实现

📢博客主页:https://blog.csdn.net/2301_779549673

📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson

📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

📢本文由 JohnKi 原创,首发于 CSDN🙉

📢未来很长,值得我们全力奔赴更美好的生活✨

文章目录


📢前言

上文中,笔者带领大家实现了 日志类 ,现在我们已经将 线程条件日志这些线程池中都会用到的基本类都模拟实现过一遍了。

线程池 是 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

现在我们将这些结合起来,深度了解一下线程池,并模拟实现一下


🏳️‍🌈一、什么是线程池

线程池的意义

提高线程复用率,避免频繁创建/销毁线程的开销

统一管理任务调度,提升系统稳定性

线程池的应用场景:

  • 需要大量的线程来完成任务,且完成任务的时间比较短。比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误.

线程池的种类

创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执行任务对象中

  1. 的任务接口
  2. 浮动线程池,其他同上

此处,我们选择固定线程个数的线程池。

🏳️‍🌈二、线程池逻辑框架

整体架构

这是一个 ​通用线程池模板类,基于 ​生产者-消费者模型,实现多线程任务的异步调度与执行。核心组件包括:

  • ​任务队列:存储待处理任务,支持线程安全操作。
  • ​工作线程组:从队列中获取任务并执行。
  • ​同步机制:互斥锁(Mutex)与条件变量(Cond)确保线程安全。
bash 复制代码
namespace ThreadPoolModule {
using namespace LogModule;
using namespace ThreadModule;
using namespace LockModule;
using namespace CondModule;

using thread_t = std::shared_ptr<Thread>;
const static int defaultnum = 5;

template <typename T> class ThreadPool {
private:
    // 判断线程池是否为空
    bool IsEmpty() { return _taskq.empty(); }
    // 工作线程的主循环逻辑
    void HandlerTask(std::string name);

public:
    ThreadPool(int num = defaultnum)
        : _num(num), _wait_num(0), _isrunning(false);
    void Equeue(T&& in); // 任务入队
    void Start();        // 启动线程池
    void Wait();         // 等待线程池结束
    void Stop();         // 停止线程池
    ~ThreadPool();       // 析构函数

private:
    std::vector<thread_t> _threads;
    int _num;
    int _wait_num;
    std::queue<T> _taskq; // 临界资源

    Mutex _lock;
    Cond _cond;

    bool _isrunning;
};
} // namespace ThreadPoolModule

成员变量解析

🏳️‍🌈三、核心接口说明

3.1 构造函数 ThreadPool(int num)

功能 :初始化线程池,创建工作线程对象(未启动)。
​流程

  • 设置线程数量 _num。
  • 循环创建 num 个线程对象,绑定到 HandlerTask 方法。
  • 记录日志(如 构建线程Thread-1对象 ... 成功)。
bash 复制代码
ThreadPool(int num = defaultnum)
    : _numThreads(num), _wait_nums(0), _isrunning(false) {
    for (int i = 0; i < _numThreads; ++i) {
        _threads.push_back(std::make_shared<Thread>(
            std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1)));
        LOG(LogLevel::INFO)
            << "构建线程" << _threads.back()->Name() << "对象...成功";
    }
}

3.2 任务提交 Equeue(T &&in)

​功能 :向任务队列添加新任务。
​参数 :T &&in(右值引用,支持移动语义优化性能)。
​流程

  1. 加锁检查线程池是否运行(if (!_isrunning) return;)。
  2. 将任务推入队列(_taskq.push(std::move(in)))。
  3. 若有等待线程(_wait_num > 0),唤醒一个线程(_cond.Notify())。
bash 复制代码
// 任务入队
void Equeue(T&& in) {
    LockGuard lock(_mutex);
    if (!_isrunning)
        _taskq.push(std::move(
            in)); // 将任务对象 in ​高效地添加到任务队列 _taskq
    if (_wait_nums > 0)
        _cond.Notify();
}

3.3 启动线程池 Start()

​功能 :启动所有工作线程。
​流程

  1. 检查是否已运行(避免重复启动)。
  2. 设置 _isrunning = true。
  3. 遍历线程对象,调用 Start() 启动线程,记录日志。
bash 复制代码
// 启动所有线程池
void Start() {
    if (_isrunning)
        return;
    _isrunning = true;
    for (auto& thread_ptr : _threads) {
        LOG(LogLevel::INFO) << "启动线程" << thread_ptr->Name() << "...成功";
        thread_ptr->Start();
    }
}

3.4 停止线程池 Stop()

功能 :停止接收新任务,唤醒所有线程处理剩余任务后退出。
​流程

  1. 加锁设置 _isrunning = false。
  2. 若有等待线程(_wait_num > 0),唤醒所有线程(_cond.NotifyAll())。
bash 复制代码
// 通知所有线程退出。
void Stop() {
    LockGuard lock(_mutex);
    if (_isrunning) {
        _isrunning = false;
        if (_wait_nums > 0)
            _cond.NotifyAll();
    }
}

3.5 等待线程结束 Wait()

​功能 :阻塞等待所有工作线程完成任务并退出。
​流程

  • 遍历线程对象,调用 Join() 等待线程结束,记录日志。
bash 复制代码
// 阻塞等待所有工作线程退出
void Wait() {
    for (auto& thread_ptr : _threads) {
        thread_ptr->Join();
        LOG(LogLevel::INFO) << "回收线程" << thread_ptr->Name() << "...成功";
    }
}

3.6 线程主逻辑 HandlerTask(std::string name)

​功能 :工作线程的核心循环逻辑。
​流程

  1. 循环获取任务
  • 加锁检查任务队列是否为空且线程池运行中。
  • 若队列空且运行中,线程进入等待(_cond.Wait(_lock))。
  • 若队列空且线程池已停止,退出循环。
  • 取出队列头部任务(t = _taskq.front(); _taskq.pop();)。
  1. 执行任务(t(name),要求任务类型 T 实现 operator())。
  2. 线程退出时记录日志。
bash 复制代码
// 工作线程的主循环逻辑
void HandlerTask(std::string name) {
    LOG(LogLevel::INFO) << "线程:" << name << ",进入HandlerTask的逻辑";
    while (true) {
        T t;
        {
            LockGuard lock(_mutex);
            while (IsEmpty() && _isrunning) {
                _wait_nums++;
                _cond.Wait(_mutex);
                _wait_nums--;
            }

            // 任务队列为空 && 线程池退出了
            if (IsEmpty() && !_isrunning)
                break;

            // 取出任务
            t = _taskq.front();
            _taskq.pop();
        }
        // 模拟处理任务
        t(name);
    }
    LOG(LogLevel::INFO) << "线程:" << name << ",退出";
}

🏳️‍🌈四、整体代码

这下面是 ThreadPool.hpp 部分的代码,有需要其他部分的代码,可以去笔者的仓库中获取

https://gitee.com/JohnKingW/linux_test/tree/master/lesson/CSDN_code/7.ThreadPool

bash 复制代码
#pragma once

#include <queue>

#include "Log.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Thread.hpp"



namespace ThreadPoolModule{
    using namespace LogModule;
    using namespace ThreadModule;
    using namespace LockModule;
    using namespace CondModule;

    using thread_t = std::shared_ptr<Thread>; 

    const static int defaultnum = 5;

    template <typename T>
    class ThreadPool{
        public:
            // 构造函数
            ThreadPool(int num = defaultnum)
                : _numThreads(num),
                  _wait_nums(0),
                  _isrunning(false)
            {
                for(int i = 0; i < _numThreads; ++i){
                    _threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1)));
                    LOG(LogLevel::INFO) << "构建线程" << _threads.back()->Name() << "对象...成功";
                }
            }

            // 任务入队
            void Equeue(T&& in){
                LockGuard lock(_mutex);
                if(!_isrunning)
                    _taskq.push(std::move(in)); // 将任务对象 in ​高效地添加到任务队列 _taskq
                if(_wait_nums > 0)
                    _cond.Notify();
            }

            // 启动所有线程池
            void Start(){
                if(_isrunning) return;
                _isrunning = true;
                for(auto& thread_ptr : _threads){
                    LOG(LogLevel::INFO) << "启动线程" << thread_ptr->Name() << "...成功";
                    thread_ptr->Start();
                }
            }

            // 阻塞等待所有工作线程退出
            void Wait(){
                for(auto& thread_ptr : _threads){
                    thread_ptr->Join();
                    LOG(LogLevel::INFO) << "回收线程" << thread_ptr->Name() << "...成功";
                }
            }

            // 通知所有线程退出。
            void Stop(){
                LockGuard lock(_mutex);
                if(_isrunning){
                    _isrunning = false;
                    if(_wait_nums>0)
                        _cond.NotifyAll();
                }
            }

            // 析构函数
            ~ThreadPool(){}
        
        private:
            // 判断线程池是否为空
            bool IsEmpty() { return _taskq.empty(); }
            // 工作线程的主循环逻辑
            void HandlerTask(std::string name){
                LOG(LogLevel::INFO) << "线程:" << name << ",进入HandlerTask的逻辑";
                while(true){
                    T t;
                    {
                        LockGuard lock(_mutex);
                        while(IsEmpty() && _isrunning){
                            _wait_nums++;
                            _cond.Wait(_mutex);
                            _wait_nums--;
                        }

                        // 任务队列为空 && 线程池退出了
                        if(IsEmpty() && !_isrunning)
                            break;

                        // 取出任务
                        t = _taskq.front();
                        _taskq.pop();
                    }
                    // 模拟处理任务
                    t(name);
                }
                LOG(LogLevel::INFO) << "线程:" << name << ",退出";
            }

        private:
            std::vector<thread_t> _threads;     // 线程池

            int _numThreads;                   // 线程池中线程的数量
            int _wait_nums;                    // 等待线程的数量
            std::queue<T> _taskq;              // 临界资源,生产者资源剩余量

            Mutex _mutex;                      // 互斥锁
            Cond _cond;                        // 条件变量

            bool _isrunning;                   // 线程池是否正在运行
    };
}

👥总结

本篇博文对 【Linux】线程池详解及基本实现 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关推荐
MarkHD1 小时前
第八天 - paramiko/ssh模块 - 远程服务器管理 - 练习:批量服务器命令执行工具
运维·服务器·ssh
写代码的小王吧2 小时前
【安全】Web渗透测试(全流程)_渗透测试学习流程图
linux·前端·网络·学习·安全·网络安全·ssh
Tee xm3 小时前
清晰易懂的跨平台 MySQL 安装与配置教程
linux·windows·mysql·macos·安装
GalaxyPokemon3 小时前
MySQL基础 [一] - Ubuntu版本安装
linux·运维·ubuntu
musk12123 小时前
wsl2 配置ubuntu 固定ip
linux·tcp/ip·ubuntu
柳鲲鹏3 小时前
UBUNTU编译datalink
linux·运维·ubuntu
追随远方3 小时前
Ubuntu 64-bit 交叉编译 FFmpeg(高级用户指南)
linux·ubuntu·ffmpeg
GalaxyPokemon3 小时前
Muduo网络库实现 [七] - Connection模块
linux·服务器·网络
慈云数据3 小时前
构建自己的私有 Git 服务器:基于 Gitea 的轻量化部署实战指南
服务器·git·gitea
三阶码叟3 小时前
centos7 yum install docker 安装错误
运维·docker·容器