【Linux】线程池设计 + 策略模式

🌈 个人主页:Zfox_

🔥 系列专栏:Linux

目录

  • [一:🔥 线程池](#一:🔥 线程池)
    • [1-1 ⽇志与策略模式](#1-1 ⽇志与策略模式)
    • [1-2 线程池设计](#1-2 线程池设计)
    • [1-3 线程安全的单例模式](#1-3 线程安全的单例模式)
      • [1-3-1 什么是单例模式](#1-3-1 什么是单例模式)
      • [1-3-2 单例模式的特点](#1-3-2 单例模式的特点)
      • [1-3-3 饿汉实现⽅式和懒汉实现⽅式](#1-3-3 饿汉实现⽅式和懒汉实现⽅式)
      • [1-3-4 饿汉⽅式实现单例模式](#1-3-4 饿汉⽅式实现单例模式)
      • [1-3-5 懒汉⽅式实现单例模式](#1-3-5 懒汉⽅式实现单例模式)
      • [1-3-6 懒汉⽅式实现单例模式(线程安全版本)](#1-3-6 懒汉⽅式实现单例模式(线程安全版本))
    • [1-4 单例式线程池](#1-4 单例式线程池)
  • [二:🔥 共勉](#二:🔥 共勉)

一:🔥 线程池

🌶️ 下⾯开始,我们结合我们之前所做的所有封装,进⾏⼀个线程池的设计。在写之前,我们要做如下准备

1-1 ⽇志与策略模式

  • 🍧 什么是设计模式
    IT⾏业这么⽕, 涌⼊的⼈很多. 俗话说林⼦⼤了啥⻦都有. ⼤佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖⼤佬的后腿, 于是⼤佬们针对⼀些经典的常⻅的场景, 给定了⼀些对应的解决⽅案, 这个就是 设计模式
  • 🍧 ⽇志认识
    计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯具。

🍡 ⽇志格式以下⼏个指标是必须得有的:

  • 时间戳
  • ⽇志等级
  • ⽇志内容

🍡 以下⼏个指标是可选的

  • ⽂件名⾏号
  • 进程,线程相关id信息等

⽇志有现成的解决⽅案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采⽤⾃定义⽇志的⽅式。

这⾥我们采⽤ 设计模式-策略模式 来进⾏⽇志的设计。

我们想要的⽇志格式如下:

cpp 复制代码
[可读性很好的时间] [⽇志等级] [进程pid] [打印对应⽇志的⽂件名][⾏号] - 消息内容,⽀持可
变参数
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world
[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world
[2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world

模式讲解详见代码注释
Log.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem>   // c++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace LockModule;

    // 获取一下当前系统的时间
    std::string CurrentTime()
    {
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr);  // 时间戳,获取可读性较强的时间信息S

        char buffer[1024];
        // bug
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d", 
            curr.tm_year + 1900,
            curr.tm_mon + 1,
            curr.tm_mday,
            curr.tm_hour,
            curr.tm_min,
            curr.tm_sec
        );

        return buffer;
    }

    // 构成:1. 构建日志字符串 2. 刷新落盘(screen, file)
    // 1. 日志文件的默认路径和文件名
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";

    // 2. 日志等级
    enum class LogLevel
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string Level2String(LogLevel level)
    {
        switch(level)
        {
            case LogLevel::DEBUG:
                return "DEBUG";
            case LogLevel::INFO:
                return "INFO";
            case LogLevel::WARNING:
                return "WARNING";
            case LogLevel::ERROR:
                return "ERROR";
            case LogLevel::FATAL:
                return "FATAL";
            default:
                return "None";
        }
    }

    // 3. 刷新策略
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 3.1 控制台策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {}
        ~ConsoleLogStrategy()
        {}
        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);
            std::cout << message << std::endl;
        }
    private:
        Mutex _lock;
    };

    // 3.2 文件级(磁盘)策略
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &logpath = defaultlogpath, const std::string &logname = defaultlogname)
            :_logpath(logpath)
            ,_logname(logname)
        {
            // 确认_logpath是存在的
            LockGuard lockguard(_lock);

            if(std::filesystem::exists(_logpath))
            {
                return ;
            }
            try
            {
                std::filesystem::create_directories(_logpath);
            }
            catch(const std::filesystem::filesystem_error& e)
            {
                std::cerr << e.what() << '\n';
            }      
        }

        ~FileLogStrategy()
        {}

        void SyncLog(const std::string &message)
        {
            LockGuard lockguard(_lock);

            std::string log = _logpath + _logname;  // ./log/log.txt
            std::ofstream out(log, std::ios::app);  // 日志写入,一定是追加
            if(!out.is_open())
            {
                return ;
            }
            out << message << '\n';
            out.close();
        }
    private:
        std::string _logpath;
        std::string _logname;

        Mutex _lock;
    };


    // 日志类:构建日志字符串,根据策略,进行刷新
    class Logger
    {
    public:
        Logger()
        {
            // 默认采用ConsoleLogStrategy策略
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }

        void EnableConsoleLog()
        {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }

        void EnableFileLog()
        {
            _strategy = std::make_shared<FileLogStrategy>();
        }

        ~Logger()
        {}

        // 一条完整的信息:[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(<< "hello world" << 3.14 << a << b;)
        class LogMessage
        {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
                :_currtime(CurrentTime())
                ,_level(level)
                ,_pid(::getpid())
                ,_filename(filename)
                ,_line(line)
                ,_logger(logger)
            {
                std::stringstream ssbuffer;
                ssbuffer << "[" << _currtime << "] " 
                         << "[" << Level2String(_level) << "] " 
                         << "[" << _pid << "] " 
                         << "[" << _filename << "] "
                         << "[" << _line << "] - ";
                _loginfo = ssbuffer.str();
            }

            template<typename T>
            LogMessage &operator << (const T &info)
            {
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if(_logger._strategy)
                {
                    _logger._strategy->SyncLog(_loginfo);
                }
            }

        private:
            std::string _currtime;      // 当前日志的时间
            LogLevel _level;            // 日志等级
            pid_t _pid;                 // 进程pid
            std::string _filename;      // 源文件名称??
            int _line;                  // 日志所在的行号
            Logger &_logger;            // 负责根据不同的策略进行刷新
            std::string _loginfo;       // 一条完整的日志记录
        };

        // 就是要拷贝
        LogMessage operator()(LogLevel level, const std::string &filename, int line)
        {
            return LogMessage(level, filename, line, *this);         // 优化成一次构造一次析构了 连续的构造 + 拷贝构造
        }
    private:
        std::shared_ptr<LogStrategy> _strategy;     // 日志刷新的策略方案
    };

    Logger logger;

#define LOG(Level) logger(Level, __FILE__, __LINE__)
#define ENABLE_CONSOLE_LOG() logger.EnableConsoleLog()
#define ENABLE_FILE_LOG() logger.EnableFileLog()
}

🥗 使⽤样例:

cpp 复制代码
#include "Log.hpp"

using namespace LogModule;

int main()
{
    ENABLE_FILE_LOG();
    LOG(LogLevel::DEBUG) << "hello file";
    LOG(LogLevel::DEBUG) << "hello file";
    LOG(LogLevel::DEBUG) << "hello file";
    LOG(LogLevel::DEBUG) << "hello file";

    ENABLE_CONSOLE_LOG();
    LOG(LogLevel::DEBUG) << "hello world";
    LOG(LogLevel::DEBUG) << "hello world";
    LOG(LogLevel::DEBUG) << "hello world";
    LOG(LogLevel::DEBUG) << "hello world";

    return 0;
}

1-2 线程池设计

线程池:

  • 线程池通过一个线程安全的阻塞任务队列加上一个或一个以上的线程实现,线程池中的线程可以从阻塞队列中获取任务进行任务处理,当线程都处于繁忙状态时可以将任务加入阻塞队列中,等到其它的线程空闲后进行处理。
  • 可以避免大量线程频繁创建或销毁所带来的时间成本,也可以避免在峰值压力下,系统资源耗尽的风险;并且可以统一对线程池中的线程进行管理,调度监控。

💜 线程池的应⽤场景:

  • 🧁 需要⼤量的线程来完成任务,且完成任务的时间⽐较短。 ⽐如WEB服务器完成⽹⻚请求这样的任务,使⽤线程池技术是⾮常合适的。因为单个任务⼩,⽽任务数量巨⼤,你可以想象⼀个热⻔⽹站的点击次数。 但对于⻓时间的任务,⽐如⼀个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间⽐线程的创建时间⼤多了。
  • 🧁 对性能要求苛刻的应⽤,⽐如要求服务器迅速响应客⼾请求。
  • 🧁 接受突发性的⼤量请求,但不⾄于使服务器因此产⽣⼤量线程的应⽤。突发性⼤量客⼾请求,在没有线程池情况下,将产⽣⼤量线程,虽然理论上⼤部分操作系统线程数⽬最⼤值不是问题,短时间内产⽣⼤量线程可能使内存到达极限,出现错误。

🌶️ 线程池的种类

  • 创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执⾏任务对象中的任务接⼝
  • 浮动线程池,其他同上,此处,我们选择固定线程个数的线程池。


ThreadPool.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <vector>
#include <memory>
#include "Log.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Thread.hpp"

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

    // 用来做测试的线程方法
    void DefaultTest()
    {
        while (true)
        {
            LOG(LogLevel::DEBUG) << "我是一个测试方法";
            sleep(1);
        }
    }

    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)
        {
            LOG(LogLevel::INFO) << "线程: " << name << ", 进入HandlerTask的逻辑";
            while (true)
            {
                // 1. 拿任务
                T t;
                {
                    LockGuard lockguard(_lock);
                    while (IsEmpty() && _isrunning)
                    {
                        _wait_num++;
                        _cond.Wait(_lock);
                        _wait_num--;
                    }
                    // 2. 任务队列为空 && 线程池退出了
                    if(IsEmpty() && !_isrunning)
                        break;

                    t = _taskq.front();
                    _taskq.pop();

                }

                // 2. 处理任务
                t(name); // 规定,未来所有的任务处理,全部都是必须提供()方法!
            }
            LOG(LogLevel::INFO) << "线程: " << name << " 退出";
        }

    public:
        ThreadPool(int num = defaultnum) : _num(num), _wait_num(0), _isrunning(false)
        {
            for (int i = 0; i < _num; 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 lockguard(_lock);
            if(!_isrunning) return;
            _taskq.push(std::move(in));
            if(_wait_num > 0)
                _cond.Notify();
        }
        void Start()
        {
            if(_isrunning) return;
            _isrunning = true; // bug fix??
            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 lockguard(_lock);
            if(_isrunning)
            {
                // 3. 不能在入任务了
                _isrunning = false; // 不工作
                // 1. 让线程自己退出(要唤醒) && // 2. 历史的任务被处理完了
                if(_wait_num>0)
                    _cond.NotifyAll();
            }
        }
        ~ThreadPool()
        {
        }

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

        Mutex _lock;
        Cond _cond;

        bool _isrunning;
    };
}

Task.hpp

cpp 复制代码
#pragma

#include <iostream>
#include <functional>
#include "Log.hpp"

using namespace LogModule;

using task_t = std::function<void(std::string name)>;

void Push(std::string name)
{
    LOG(LogLevel::DEBUG) << "我是一个将数据推送到服务器的任务,正在被执行" << "[" << name << "]";
}

main.cc

cpp 复制代码
#include "ThreadPool.hpp"
#include "Task.hpp"
#include <memory>

using namespace ThreadPoolModule;

int main()
{
    ENABLE_FILE_LOG();


    std::unique_ptr<ThreadPool<task_t>> tp = std::make_unique<ThreadPool<task_t>>();

    tp->Start();

    int cnt = 10;
    while(cnt)
    {
        tp->Equeue(Push);
        cnt--;
        sleep(1);
    }

    tp->Stop();
    sleep(3);

    tp->Wait();
    return 0;
}
cpp 复制代码
g++ main.cc -std=c++17 -lpthread   // 需要使⽤C++17

运行结果:

cpp 复制代码
$ ./a.out
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [62] - ThreadPool
Construct()
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [70] - init thread
Thread-0 done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [70] - init thread
Thread-1 done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [70] - init thread
Thread-2 done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [70] - init thread
Thread-3 done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [70] - init thread
Thread-4 done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [79] - start thread
Thread-0done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [79] - start thread
Thread-1done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [28] - Thread-0 is
running...
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [79] - start thread
Thread-2done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [79] - start thread
Thread-3done
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [28] - Thread-3 is
running...
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [28] - Thread-2 is
running...
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [79] - start thread
Thread-4done
[2024-08-04 15:09:29] [DEBUG] [206342] [ThreadPool.hpp] [109] - 任务⼊队列成功
[2024-08-04 15:09:29] [DEBUG] [206342] [ThreadPool.hpp] [52] - Thread-0 get a
task
this is a task
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [28] - Thread-1 is
running...
[2024-08-04 15:09:29] [INFO] [206342] [ThreadPool.hpp] [28] - Thread-4 is
running...
[2024-08-04 15:09:30] [DEBUG] [206342] [ThreadPool.hpp] [109] - 任务⼊队列成功
[2024-08-04 15:09:30] [DEBUG] [206342] [ThreadPool.hpp] [52] - Thread-3 get a
task
this is a task
...
this is a task
[2024-08-04 15:09:39] [DEBUG] [206342] [ThreadPool.hpp] [88] - 线程池退出中...
[2024-08-04 15:09:44] [INFO] [206342] [ThreadPool.hpp] [95] - Thread-0 退出...
[2024-08-04 15:09:44] [INFO] [206342] [ThreadPool.hpp] [95] - Thread-1 退出...
[2024-08-04 15:09:44] [INFO] [206342] [ThreadPool.hpp] [95] - Thread-2 退出...
[2024-08-04 15:09:44] [INFO] [206342] [ThreadPool.hpp] [95] - Thread-3 退出...
[2024-08-04 15:09:44] [INFO] [206342] [ThreadPool.hpp] [95] - Thread-4 退出..

1-3 线程安全的单例模式

1-3-1 什么是单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点来访问这个实例。在C++中,单例模式通常用于需要控制资源访问或管理全局状态的情况下,比如日志记录器、配置管理器、线程池等。

1-3-2 单例模式的特点

某些类, 只应该具有⼀个对象(实例), 就称之为单例. 例如⼀个男⼈只能有⼀个媳妇.

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要⽤⼀个单例

的类来管理这些数据.

1-3-3 饿汉实现⽅式和懒汉实现⽅式

🍱 [洗碗的例⼦]

cpp 复制代码
吃完饭, ⽴刻洗碗, 这种就是饿汉⽅式. 因为下⼀顿吃的时候可以⽴刻拿着碗就能吃饭.
吃完饭, 先把碗放下, 然后下⼀顿饭⽤到这个碗了再洗碗, 就是懒汉⽅式.

懒汉⽅式最核⼼的思想是 "延时加载". 从⽽能够优化服务器的启动速度.

1-3-4 饿汉⽅式实现单例模式

cpp 复制代码
template <typename T>
class Singleton {
	static T data;
public:
	static T* GetInstance() {
		return &data;
	}
};

🍱 - 只要通过 Singleton 这个包装类来使⽤ T 对象, 则⼀个进程中只有⼀个 T 对象的实例。

1-3-5 懒汉⽅式实现单例模式

cpp 复制代码
template <typename T>
class Singleton {
	static T* inst;
public:
	static T* GetInstance() {
		if (inst == NULL) {
			inst = new T();
		} 
		return inst;
	}
};

存在⼀个严重的问题, 线程不安全.
第⼀次调⽤ GetInstance 的时候, 如果两个线程同时调⽤, 可能会创建出两份 T 对象的实例.
但是后续再次调⽤, 就没有问题了.

1-3-6 懒汉⽅式实现单例模式(线程安全版本)

cpp 复制代码
// 懒汉模式, 线程安全
template <typename T>
class Singleton {
	volatile static T* inst;    // 需要设置 volatile 关键字, 否则可能被编译器优化.
	static std::mutex lock;
public:
	static T* GetInstance() {
		if (inst == NULL) {     // 双重判定空指针, 降低锁冲突的概率, 提⾼性能.
			lock.lock();        // 使⽤互斥锁, 保证多线程情况下也只调⽤⼀次 new.
			if (inst == NULL) {
				inst = new T();
			} 
				lock.unlock();
		} 
		return inst;
	}
}

注意事项:

  1. 加锁解锁的位置
  2. 双重 if 判定, 避免不必要的锁竞争
  3. volatile关键字防⽌过度优化 (指令重排序和从寄存器中读取数据 ) (可见性和有序性)

1-4 单例式线程池

ThreadPool.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <queue>
#include <vector>
#include <memory>
#include "Mutex.hpp"
#include "Log.hpp"
#include "Cond.hpp"
#include "Thread.hpp"

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

    // 我是来做测试的线程方法
    void DefaultTest()
    {
        while (true)
        {
            LOG(LogLevel::DEBUG) << "我是一个线程方法";
            ::sleep(1);
        }
    }

    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)
        {
            LOG(LogLevel::INFO) << "线程: " << name << ", 进入了HandletTask的执行逻辑";
            while (true)
            {
                // 1. 拿任务
                T t;
                {
                    LockGuard lockguard(_lock);
                    while (IsEmpty() && _isrunning)
                    {
                        _wait_num++;
                        _cond.Wait(_lock);
                        _wait_num--;
                    }
                    // 2. 任务队列为空 && 线程池退出了
                    if (IsEmpty() && !_isrunning)
                        break;

                    t = _taskq.front();
                    _taskq.pop();
                }

                // 2. 处理任务
                t(name); // 规定 所有的任务处理 全部提供()方法
            }
            LOG(LogLevel::INFO) << "线程: " << name << " 退出";
        }
        ThreadPool(const ThreadPool<T> &) = delete;
        const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

        ThreadPool(int num = defaultnum)
            : _num(num), _wait_num(0), _isrunning(false)
        {
            for (int i = 0; i < _num; i++)
            {
                _threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1)));
                LOG(LogLevel::DEBUG) << "构建线程" << _threads.back()->Name() << "对象 ... 成功";
            }
        }

    public:
        static ThreadPool<T> *getInstance()
        {
            if (instance == nullptr)
            {
                LockGuard lockguard(mutex);
                if (instance == nullptr)
                {
                    LOG(LogLevel::INFO) << "单例首次被执行,需要加载对象...";
                    instance = new ThreadPool<T>();
                }
            }
            return instance;
        }

        void Equeue(T &&in)
        {
            LockGuard lockguard(_lock);
            if (!_isrunning)
                return;

            _taskq.push(std::move(in));
            if (_wait_num > 0)
                _cond.Notify();
        }

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

        void Wait()
        {
            for (auto &thread_ptr : _threads)
            {
                thread_ptr->Join();
                LOG(LogLevel::INFO) << "回收线程" << thread_ptr->Name() << " ... 成功";
            }
        }

        void Stop()
        {
            LockGuard lockguard(_lock);
            if (_isrunning)
            {
                // 3. 不能再入任务了
                _isrunning = false; // 不工作

                // 1. 让线程自己退出(要唤醒) && 2. 历史的任务被处理完了
                if (_wait_num > 0)
                    _cond.NotifyAll();
            }
        }

        ~ThreadPool()
        {
        }

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

        Mutex _lock;
        Cond _cond;

        bool _isrunning;

        static ThreadPool<T> *instance;
        static Mutex mutex; // 只用来保护单例
    };

    template <typename T>
    ThreadPool<T> *ThreadPool<T>::instance = nullptr;

    template <typename T>
    Mutex ThreadPool<T>::mutex;
}

测试代码

cpp 复制代码
#include "ThreadPool.hpp"
#include "Task.hpp"
#include <memory>

using namespace ThreadPoolModule;

int main()
{
    ENABLE_CONSOLE_LOG();

    ThreadPool<task_t>::getInstance()->Start();


    int cnt = 10;
    while(cnt)
    {
        ThreadPool<task_t>::getInstance()->Equeue(Push);
        cnt--;
        sleep(1);
    }

    ThreadPool<task_t>::getInstance()->Stop();
    sleep(3);

    ThreadPool<task_t>::getInstance()->Wait();
    
    return 0;
}

运行结果:

cpp 复制代码
root@hcss-ecs-a9ee:~/code/linux/112/lesson32/2.ThreadPool# ./thread_pool 
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [89] - 单例首次被执行,需要加载对象...
[2024-11-29 10:51:04] [DEBUG] [400187] [ThreadPool.hpp] [77] - 构建线程Thread-1对象 ... 成功
[2024-11-29 10:51:04] [DEBUG] [400187] [ThreadPool.hpp] [77] - 构建线程Thread-2对象 ... 成功
[2024-11-29 10:51:04] [DEBUG] [400187] [ThreadPool.hpp] [77] - 构建线程Thread-3对象 ... 成功
[2024-11-29 10:51:04] [DEBUG] [400187] [ThreadPool.hpp] [77] - 构建线程Thread-4对象 ... 成功
[2024-11-29 10:51:04] [DEBUG] [400187] [ThreadPool.hpp] [77] - 构建线程Thread-5对象 ... 成功
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [115] - 启动线程Thread-1 ... 成功
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [115] - 启动线程Thread-2 ... 成功
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [115] - 启动线程Thread-3 ... 成功
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [115] - 启动线程Thread-4 ... 成功
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [115] - 启动线程Thread-5 ... 成功
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [42] - 线程: Thread-3, 进入了HandletTask的执行逻辑
[2024-11-29 10:51:04] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-3]
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [42] - 线程: Thread-2, 进入了HandletTask的执行逻辑
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [42] - 线程: Thread-1, 进入了HandletTask的执行逻辑
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [42] - 线程: Thread-5, 进入了HandletTask的执行逻辑
[2024-11-29 10:51:04] [INFO] [400187] [ThreadPool.hpp] [42] - 线程: Thread-4, 进入了HandletTask的执行逻辑
[2024-11-29 10:51:05] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-3]
[2024-11-29 10:51:06] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-2]
[2024-11-29 10:51:07] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-1]
[2024-11-29 10:51:08] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-5]
[2024-11-29 10:51:09] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-4]
[2024-11-29 10:51:10] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-3]
[2024-11-29 10:51:11] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-2]
[2024-11-29 10:51:12] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-1]
[2024-11-29 10:51:13] [DEBUG] [400187] [Task.hpp] [13] - 我是一个将数据推送到服务器的任务,正在被执行[Thread-5]
[2024-11-29 10:51:14] [INFO] [400187] [ThreadPool.hpp] [66] - 线程: Thread-2 退出
[2024-11-29 10:51:14] [INFO] [400187] [ThreadPool.hpp] [66] - 线程: Thread-3 退出
[2024-11-29 10:51:14] [INFO] [400187] [ThreadPool.hpp] [66] - 线程: Thread-1 退出
[2024-11-29 10:51:14] [INFO] [400187] [ThreadPool.hpp] [66] - 线程: Thread-5 退出
[2024-11-29 10:51:14] [INFO] [400187] [ThreadPool.hpp] [66] - 线程: Thread-4 退出
[2024-11-29 10:51:17] [INFO] [400187] [ThreadPool.hpp] [124] - 回收线程Thread-1 ... 成功
[2024-11-29 10:51:17] [INFO] [400187] [ThreadPool.hpp] [124] - 回收线程Thread-2 ... 成功
[2024-11-29 10:51:17] [INFO] [400187] [ThreadPool.hpp] [124] - 回收线程Thread-3 ... 成功
[2024-11-29 10:51:17] [INFO] [400187] [ThreadPool.hpp] [124] - 回收线程Thread-4 ... 成功
[2024-11-29 10:51:17] [INFO] [400187] [ThreadPool.hpp] [124] - 回收线程Thread-5 ... 成功

二:🔥 共勉

以上就是我对 【Linux】线程池设计 + 策略模式 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉

相关推荐
算法黑哥15 分钟前
服务器挖矿
运维·服务器
erxij22 分钟前
【游戏引擎之路】登神长阶(十五)——DirectX12龙书:行百里者半九十(学习阶段完结)
c++·经验分享·学习·游戏·3d·游戏引擎
徐浪老师30 分钟前
C语言解决空瓶换水问题:高效算法与实现
c语言·开发语言·算法
style-h38 分钟前
C语言——指针初阶(二)
c语言·开发语言
洪大宇39 分钟前
开发一套ERP 第八弹 RUst 插入数据
linux
想和我重名?1 小时前
Linux下的wlan0控制
linux·嵌入式
ydm_ymz1 小时前
C语言进阶7:程序环境与预处理
c语言·开发语言·数据结构
天赐细莲1 小时前
(C语言) 8大翻译阶段
c语言·开发语言·c++·windows·微软·编辑器
LeonNo111 小时前
Linux:systemd进程管理【1】
linux·运维·服务器
꧁༺摩༒西༻꧂1 小时前
服务器命令行复制文件
运维·服务器