【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】线程池详解及基本实现 做了一个较为详细的介绍,不知道对你有没有帮助呢

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

相关推荐
泛黄的咖啡店27 分钟前
域名系统DNS
运维·云计算
dessler36 分钟前
Kubernetes(k8s)-集群监控(Prometheus)
linux·运维·kubernetes
一夜沐白37 分钟前
Linux用户管理
linux·运维·服务器·笔记
PLUS_WAVE1 小时前
【Tools】chezmoi 跨多台不同的机器管理 dotfiles 的工具
linux·服务器·软件工程·工具·chezmoi
喝养乐多长不高1 小时前
详细PostMan的安装和基本使用方法
java·服务器·前端·网络协议·测试工具·https·postman
薯条不要番茄酱2 小时前
【网络原理】从零开始深入理解TCP的各项特性和机制.(二)
服务器·网络·tcp/ip
Pasregret2 小时前
备忘录模式:实现对象状态撤销与恢复的设计模式
运维·服务器·设计模式
唐青枫2 小时前
Linux man 命令使用教程
linux
LuckyRich12 小时前
【仿Mudou库one thread per loop式并发服务器实现】服务器边缘测试+性能测试
服务器·c++
珹洺3 小时前
Linux红帽:RHCSA认证知识讲解(十 四)分区管理、交换分区,创建逻辑卷与调整逻辑卷的大小
linux·运维·服务器