Bootstrap

【Linux线程】之线程池

在这里插入图片描述

📃博客主页: 小镇敲码人
💚代码仓库,欢迎访问
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞


线程池

什么是线程池

线程池是一种管理和复用线程资源的设计模式。它通过创建和维护一批线程来执行任务,从而避免频繁的线程创建和销毁开销。线程池的核心思想是将线程视作一种可复用的资源,为需要执行的任务提供线程支持

线程池的组成部分

  1. 线程的管理模块:负责创建、启动、等待(销毁)线程。

  2. 任务队列:存储线程要执行的任务。

  3. 线程同步机制

    • 互斥锁锁用来保护共享资源。
    • 条件变量用来通知和阻塞线程(同步线程和任务队列,确保没有任务时线程不会继续取任务)。
    • 计数变量,用来存储当前线程池中挂起的线程数量,当有新任务到来时,如果这个值大于0,就调用条件变量通知函数。
  4. 单例模式(可选):使用懒汉模式或者饿汉模式可以让线程池在程序运行中始终只会存在一个对象,避免重复创建,提高系统稳定性。

  5. 线程池的控制接口:提供停止线程池、向线程池中Push任务的接口。

为什么要有线程池

  1. 提高系统效率:创建和销毁线程需要调用系统调用,是很昂贵的操作,需要耗费资源(内存、时间)。但是在线程池中线程被复用,避免了频繁的创建和销毁。

  2. 控制并发规模,防止资源耗尽

    在高并发场景,如果线程规模无限增长下去,可能导致如下问题:

    • 内存资源耗尽:线程的创建是需要内核在内存中分配空间的,过多的线程会导致内存资源耗尽(每个线程都有独立的栈空间和寄存器资源)。
    • CPU过载:线程频繁的切换导致CPU的性能下降,让执行任务的速度变慢。可能某一个线程正在执行一个任务,还没有执行完就被切换了,由于系统中线程数量很多,要过很多才能轮到它调度,那它的任务就需要很长时间才能处理完。
    • 系统崩溃:内存资源不足后无法分配新线程,程序无法继续运行。

    线程池通过限制线程数量解决了上述问题。

  3. 封装线程的创建和启动:与主程序解耦合,增强了代码的可维护性,主程序中可重点关注业务逻辑,而不需要关心线程是如何启动和创建的,线程池帮我们做了,使用单例模式还可以隐藏这些细节。

线程池的实现

线程池中的方法

对外的方法
  1. void Stop():
    • 功能:停止线程池的工作。
  2. static ThreadPool<T>* GetInstance()
    • 功能:创建和获取线程池单例。如果线程池单例已经创建,则调用该单例可以获取它。
    • 细节:该函数可能在多线程中被调用(被重入),要注意对共享资源的保护,防止因为切换线程和非原子操作造成数据不一致的情况(该单例被创建多次可能,违背了单例的初衷)。
  3. void EnqueueTask(T& t):
    • 功能:向任务队列中push任务。
  4. size_t GetQueueSize():
    • 功能:返回任务队列中的任务的数量。
私有方法
  1. 构造函数
    • 功能:创建一个线程池对象并初始化相关属性。因为是单例模式,构造函数必须私有,线程池的拷贝和复制函数也要禁用。
  2. void Creat_threads()
    • 功能:创建线程池中的线程对象。
  3. void Start_threads():
    • 功能:让所有的线程开始工作。
  4. void HandlerTask(string& name)
    • 功能:线程要调用的任务处理函数。
线程池的属性
  1. int _wait_thread_num

    • 描述:当线程在任务队列中取数据时,可能由于任务队列为空陷入等待。这个参数维护等待的线程的数量。
  2. vector<Thread*> _threads :

    • 描述:存储线程的容器。
  3. int _threadnum

    • 描述:线程池中的线程数量。
  4. queue<T> _task_queue

    • 描述:任务队列,存储待执行的任务。
  5. int _wait_thread_num

    • 描述:当前等待的线程数量。
  6. pthread_mutex_t _queue_lock

    • 描述:互斥锁,保护任务队列。
  7. pthread_cond_t _cond

    • 描述:条件变量,用于线程同步。
  8. static ThreadPool<T>* _instance

    • 描述:线程池单例实例
  9. static pthread_mutex_t _instance_lock

    • 描述:保护单例实例的锁。
  10. bool _IsRunning

    • 描述:线程池运行状态。

代码实现

Task.hpp:

#pragma once
#include <string>

// 定义 Task 类,表示一个简单的任务
class Task
{
public:
    // 构造函数,初始化任务的操作数 a 和 b,以及结果 result
    Task(int a, int b) : _a(a), _b(b), _result(0) {}

    // 执行任务的具体逻辑:计算 a 和 b 的和
    void Execute()
    {
        _result = _a + _b; // 计算结果并存储在 _result 中
    }

    // 重载函数调用运算符,使对象可以像函数一样调用
    void operator()()
    {
        Execute(); // 调用 Execute 函数
    }

    // 获取任务的调试信息,返回运算的表达式但不包含结果
    std::string DebugToString()
    {
        return std::to_string(_a) + "+" + std::to_string(_b) + " = ?"; // 返回形如 "a + b = ?" 的字符串
    }

    // 获取任务执行后的结果信息,返回完整的运算表达式和结果
    std::string ResultToString()
    {
        return std::to_string(_a) + "+" + std::to_string(_b) + " = " + std::to_string(_result); // 返回形如 "a + b = result" 的字符串
    }

    // 析构函数,用于清理资源
    ~Task() {}

private:
    int _a;      // 操作数 a
    int _b;      // 操作数 b
    int _result; // 任务的计算结果
};

Thread.hpp:线程池里面的任务队列存储了线程需要处理的任务,线程对象不再关心任务相关的属性,它只关心线程的相关属性和行为。

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>

// 定义一个命名空间,防止类名与其他代码冲突
namespace ThreadModule
{
    // 使用模板定义一个通用的函数类型别名,参数是类型为 T 的引用
    using func_t = std::function<void(std::string&)>;

    // 定义一个模板类 Thread,用于封装 pthread 的线程创建与管理
    class Thread
    {
    private:
        func_t _func;             // 线程的执行函数,接受一个 T 类型的引用参数
        std::string _threadname;     // 线程名称
        pthread_t _tid;              // 线程 ID
        bool _stop;                  // 线程是否处于停止状态的标志

    public:
        // 构造函数,初始化线程执行函数、任务参数和线程名称
        Thread(func_t func,const std::string& threadname = "none-thread")
            : _func(func),_threadname(threadname), _stop(true)
        {}

        // 线程入口函数,必须是静态方法以符合 pthread_create 的接口要求
        static void* threadroutine(void* args)
        {
            Thread* this_ = static_cast<Thread*>(args); // 将 void* 转为当前类指针
            this_->_func(this_->_threadname);  // 执行线程的任务函数
            return nullptr;                // 返回空指针作为线程的返回值
        }

        // 启动线程
        bool Start()
        {
            // 使用 pthread_create 创建线程,并将当前类对象作为参数传递给线程入口函数
            int n = pthread_create(&_tid, nullptr, threadroutine, this);
            if (!n) // 如果线程创建成功
            {
                _stop = false; // 更新线程状态为运行中
                return true;
            }
            else
            {
                return false; // 创建失败,返回 false
            }
        }

        // 等待线程执行完成(线程阻塞并回收)
        void Join()
        {
            if (!_stop) // 如果线程未停止
            {
                pthread_join(_tid, nullptr); // 等待线程完成
            }
        }

        // 分离线程(让线程独立运行,主线程不再管理它的生命周期)
        void Detach()
        {
            if (!_stop) // 如果线程未停止
            {
                pthread_detach(_tid); // 分离线程
            }
        }

        // 获取线程名称
        std::string name()
        {
            return _threadname;
        }

        // 析构函数
        ~Thread() {}
    };
}

ThreadPool.hpp:

#pragma once
#include "Thread.hpp"
#include <pthread.h>
#include <queue>
#include "LockGuard.hpp"
#include "Task.hpp"
using namespace std;
using namespace ThreadModule;

#define DEFAULTNUM 10 // 默认线程池中的线程数量

namespace ThreadPoolModule
{

// 模板类 ThreadPool,用于管理线程池
template<class T>
class ThreadPool
{
private:
    // 私有构造函数,初始化线程池
    ThreadPool(int threadnum = DEFAULTNUM)
    : _threadnum(threadnum)
    {
        pthread_mutex_init(&_queue_lock, nullptr); // 初始化互斥锁
        pthread_cond_init(&_cond, nullptr);       // 初始化条件变量
    }

    // 创建线程对象并加入线程池
    void Creat_thread()
    {
        for (int i = 0; i < _threadnum; ++i)
        {
            // 创建 Thread 对象并绑定线程池的任务处理函数
            _threads.emplace_back(new Thread(bind(&ThreadPool<T>::HandlerTask, this, placeholders::_1)));
        }
        _IsRunning = true; // 标记线程池正在运行
    }

    // 启动线程池中的所有线程
    void Start_Thread()
    {
        for (auto& thread : _threads)
        {
            thread->Start(); // 启动每个线程
        }
    }

    // 等待所有线程执行完成并释放资源
    void Wait_All_Thread()
    {
        for (auto& thread : _threads)
        {
            thread->Join(); // 等待线程结束
            delete thread;  // 释放线程对象
        }
    }

    // 唤醒一个等待线程
    void Thread_Wakeup()
    {
        pthread_cond_signal(&_cond); // 通知一个线程
    }

    // 唤醒所有等待线程
    void Thread_All_Wakeup()
    {
        pthread_cond_broadcast(&_cond); // 通知所有线程
    }

    // 让线程进入等待状态
    void Thread_Sleep()
    {
        pthread_cond_wait(&_cond, &_queue_lock); // 线程等待条件变量
    }

    // 线程任务处理函数
    void HandlerTask(string& name)
    {
        while (true)
        {
            LockGurad guard(_queue_lock); // 加锁保护队列

            // 当任务队列为空且线程池仍在运行时,线程进入等待
            while (_task_queue.empty() && _IsRunning)
            {
                _wait_thread_num++; // 增加等待线程数量
                Thread_Sleep();     // 进入等待
                _wait_thread_num--; // 减少等待线程数量
            }

            // 如果任务队列为空且线程池停止运行,退出循环
            if (_task_queue.empty() && !_IsRunning)
            {
                break;
            }

            // 从任务队列中取出任务并执行
            T t = _task_queue.front();
            _task_queue.pop();
            t(); // 执行任务
            cout << t.ResultToString() << endl; // 输出任务结果
        }
    }

    // 禁用拷贝构造函数和赋值运算符,防止对象拷贝
    ThreadPool<T>(const ThreadPool<T>&) = delete;
    ThreadPool<T>& operator=(const ThreadPool<T>&) = delete;

public:
    // 获取单例实例
    static ThreadPool<T>* GetInstance()
    {
        if (_instance == nullptr)
        {
            LockGurad guard(_instance_lock); // 加锁保护单例实例
            if (_instance == nullptr)
            {
                _instance = new ThreadPool<T>(10); // 创建线程池实例
                _instance->Creat_thread();        // 创建线程
                _instance->Start_Thread();        // 启动线程
            }
        }
        return _instance;
    }

    // 将任务加入任务队列
    bool EnqueueTask(const T& t)
    {
        LockGurad gurad(_queue_lock); // 加锁保护任务队列
        bool ret = false;
        if (_IsRunning)
        {
            _task_queue.push(t); // 添加任务到队列
            if (_wait_thread_num > 0)
                Thread_Wakeup(); // 唤醒等待线程
            ret = true;
        }
        return ret;
    }

    // 获取当前任务队列的大小
    size_t GetQueueSize()
    {
        LockGurad guard(_queue_lock); // 加锁保护任务队列
        return _task_queue.size();
    }

    // 停止线程池运行
    void Stop()
    {
        LockGurad gurad(_queue_lock); // 加锁保护任务队列
        _IsRunning = false;           // 标记线程池停止运行
        Thread_All_Wakeup();          // 唤醒所有线程
    }

    // 析构函数,清理资源
    ~ThreadPool()
    {
        _instance->Wait_All_Thread(); // 等待所有线程完成
        pthread_mutex_destroy(&_queue_lock); // 销毁互斥锁
        pthread_cond_destroy(&_cond);       // 销毁条件变量
    }

private:
    vector<Thread*> _threads;         // 存储线程的容器
    int _threadnum;                   // 线程池中的线程数量
    queue<T> _task_queue;             // 任务队列,存储待执行的任务
    int _wait_thread_num;             // 当前等待的线程数量
    pthread_mutex_t _queue_lock;      // 互斥锁,保护任务队列
    pthread_cond_t _cond;             // 条件变量,用于线程同步
    static ThreadPool<T>* _instance;  // 线程池单例实例
    static pthread_mutex_t _instance_lock; // 单例实例锁
    bool _IsRunning;                  // 线程池运行状态
};

// 初始化静态成员变量
template<class T>
ThreadPool<T>* ThreadPool<T>::_instance = nullptr;

template<class T>
pthread_mutex_t ThreadPool<T>::_instance_lock = PTHREAD_MUTEX_INITIALIZER;

}

Main.cc:

#include "ThreadPool.hpp" // 包含线程池类的头文件
#include <memory>         // 包含 std::unique_ptr 的定义
#include "Task.hpp"       // 包含任务类的头文件
#include <unistd.h>       // 提供 usleep 和 sleep 函数

using namespace ThreadPoolModule;

int main()
{
    // 创建一个线程池实例,使用 std::unique_ptr 管理线程池的生命周期
    // ThreadPool<Task>::GetInstance() 返回一个线程池的单例
    std::unique_ptr<ThreadPool<Task>> tp(ThreadPool<Task>::GetInstance());

    int tasknum = 10; // 定义需要创建的任务数量
    while (tasknum)   // 循环创建任务
    {
        // 生成随机数作为任务的操作数 a 和 b
        int a = rand() % 10 + 1; // 随机生成 1 到 10 的数
        usleep(1234);            // 延时 1.234 毫秒,增加随机性
        int b = rand() % 5 + 1;  // 随机生成 1 到 5 的数

        // 创建一个任务对象,任务内容是计算 a + b
        Task t(a, b);

        // 输出任务的调试信息,例如 "a + b = ?"
        cout << t.DebugToString() << endl;

        // 将任务添加到线程池的任务队列中
        tp->EnqueueTask(t);

        // 等待 1 秒,模拟任务创建的间隔
        sleep(1);

        // 减少任务数量
        tasknum--;
    }

    // 停止线程池,通知所有工作线程结束
    tp->Stop();

    return 0; // 程序正常结束
}

运行结果:
image-20241231183651181

  • 本人知识、能力有限,若有错漏,烦请指正,非常非常感谢!!!
  • 转发或者引用需标明来源。
;