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

news2024/12/23 17:48:34

🌈 个人主页:Zfox_
🔥 系列专栏:Linux

目录

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

一:🔥 线程池

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

  • 准备线程的封装
  • 准备锁和条件变量的封装
  • 引⼊⽇志,对线程进⾏封装
    这里用到了我们上一篇博客用到的头文件及代码

1-1 ⽇志与策略模式

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

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

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

🍡 以下⼏个指标是可选的

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

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

这⾥我们采⽤ 设计模式-策略模式 来进⾏⽇志的设计。
我们想要的⽇志格式如下:

[可读性很好的时间] [⽇志等级] [进程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

#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()
}

🥗 使⽤样例:

#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

#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

#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

#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;
}
g++ main.cc -std=c++17 -lpthread   // 需要使⽤C++17

运行结果:

$ ./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 饿汉实现⽅式和懒汉实现⽅式

🍱 [洗碗的例⼦]

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

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

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

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

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

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

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 懒汉⽅式实现单例模式(线程安全版本)

// 懒汉模式, 线程安全
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

#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;
}

测试代码

#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;
}

运行结果:

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】线程池设计 + 策略模式 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252463.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vim插件管理器vim-plug替代vim-bundle

文章目录 vim-plug与vim-bundle对比vim-plug安装vim-plug管理安装插件相关文章 vim-plug与vim-bundle对比 vim-plug 和 vim-bundle 都是 Vim 的插件管理器&#xff0c;但它们有一些关键的区别。以下是两者的主要对比&#xff1a; 易用性和简洁性 vim-plug: 易用性: vim-plug …

107.【C语言】数据结构之二叉树求总节点和第K层节点的个数

目录 1.求二叉树总的节点的个数 1.容易想到的方法 代码 缺陷 思考:能否在TreeSize函数内定义静态变量解决size的问题呢? 其他写法 运行结果 2.最好的方法:分而治之 代码 运行结果 2.求二叉树第K层节点的个数 错误代码 运行结果 修正 运行结果 其他写法 1.求二…

【代码随想录day48】【C++复健】739. 每日温度;496.下一个更大元素 I;503.下一个更大元素II

739. 每日温度 一顿操作猛如虎&#xff0c;一看击败5%。一眼顶针&#xff0c;鉴定为在存栈的时候把值和下标一起存了&#xff0c;所以导致了问题。 class Solution { public:vector<int> dailyTemperatures(vector<int>& temperatures) {stack<vector<…

vscode + conda + qt联合开发

安装vscode 安装conda 清华大学开源软件镜像(Anaconda下载)_清华大学镜像-CSDN博客 conda create新建一个环境&#xff0c;激活这个环境&#xff0c;然后安装pyside6 pip install pyside6 -i https://pypi.tuna.tsinghua.edu.cn/simple 安装成功后输入 pip list查看是否安装…

第十一篇 绘图matplotlib.pyplot的使用

文章目录 摘要安装方法入门案例使用plt绘图使用ax绘图plt.figure参数plot参数案例一 绘制红色实心的点状图案例二 绘制红色的破折线图案例三 绘制两条线颜色总结设置标题、轴名称、图例使用plt实现绘图使用ax实现绘图legend()中loc设置刻度plt自定义刻度ax自定义刻度plt.title …

Unity-Particle System属性介绍(一)基本属性

什么是ParticleSystem 粒子系统是Unity中用于模拟大量粒子的行为的组件。每个粒子都有一个生命周期&#xff0c;包括出生、运动、颜色变化、大小变化和死亡等。粒子系统可以用来创建烟雾、火焰、水、雨、雪、尘埃、闪电和其他各种视觉效果。 开始 在项目文件下创建一个Vfx文件…

计算机的错误计算(一百七十二)

摘要 探讨 MATLAB 对于算式 的计算误差。 例1. 在 MATLAB 中计算 的值。 直接贴图吧&#xff1a; 这样&#xff0c;MATLAB 的输出中只有3位正确数字&#xff0c;有效数字的错误率为 (16-3)/16 81.25% . 因为16位的正确输出为 0.2971242332737277e-18&#xff08;ISReals…

Flink四大基石之CheckPoint(检查点) 的使用详解

目录 一、Checkpoint 剖析 State 与 Checkpoint 概念区分 设置 Checkpoint 实战 执行代码所需的服务与遇到的问题 二、重启策略解读 重启策略意义 代码示例与效果展示 三、SavePoint 与 Checkpoint 异同 操作步骤详解 四、总结 在大数据流式处理领域&#xff0c;Ap…

S4 UPA of AA :新资产会计概览

通用并行会计&#xff08;Universal Parallel Accounting&#xff09;可以支持每个独立的分类账与其他模块集成&#xff0c;UPA主要是为了支持平行评估、多货币类型、财务合并、多准则财务报告的复杂业务需求 在ML层面UPA允许根据不同的分类账规则对物料进行评估&#xff0c;并…

Vue3学习宝典

1.ref函数调用的方式生成响应式数据&#xff0c;可以传复杂和简单数据类型 <script setup> // reactive接收一个对象类型的数据 import { reactive } from vue;// ref用函数调用的方式生成响应式数据&#xff0c;可以传复杂和简单数据类型 import { ref } from vue // 简…

Linux——基础命令(2) 文件内容操作

目录 ​编辑 文件内容操作 1.Vim &#xff08;1&#xff09;移动光标 &#xff08;2&#xff09;复制 &#xff08;3&#xff09;剪切 &#xff08;4&#xff09;删除 &#xff08;5&#xff09;粘贴 &#xff08;6&#xff09;替换,撤销,查找 &#xff08;7&#xff…

openwrt利用nftables在校园网环境下开启nat6 (ipv6 nat)

年初写过一篇openwrt在校园网环境下开启ipv6 nat的文章&#xff0c;利用ip6tables控制ipv6的流量。然而从OpenWrt22版本开始&#xff0c;系统内置的防火墙变为nftables&#xff0c;因此配置方法有所改变。本文主要参考了OpenWRT使用nftables实现IPv6 NAT 这篇文章。 友情提示 …

go语言的成神之路-筑基篇-gin框架渲染模板

第一节-gin框架渲染模板 因为电脑打不开了&#xff0c;所以用朋友的电脑来写的&#xff0c;也是体验了一次从零开始用vscode配置环境&#xff0c;忙活了一上午才配置好环境。太难配置了。好了废话不多说开始今天的进修之旅。 今天开始gin框架的正式学习希望大家认真观看并检查…

【软考网工笔记】网络基础理论——网络层

文章目录 中断处理过程数据包组装RIPRSVPipv4RIPv1 & RIPv2HFC 混合光纤同轴电缆&#xff08;Hybrid Fiber Coax&#xff0c;简称HFC&#xff09;BGP (边界网关协议)BGP-4 协议的四种报文ICMP 协议数字语音电子邮件协议MPLS 多协议标记交换ipv6DHCPDNS名称解析过程查询顺序…

linux网络抓包工具

linux网络抓包工具 一、tcpdump1.1 基本用法1.2 龙芯平台实例操作 二、wireshark2.1 主要功能2.2 龙芯平台实例操作 一、tcpdump tcpdump 指令可列出经过指定网络界面的数据包文件头&#xff0c;可以将网络中传送的数据包的 “头” 完全截获下来提供分析。它支持针对网络层、协…

NaviveUI框架的使用 ——安装与引入(图标安装与引入)

文章目录 概述安装直接引入引入图标样式库 概述 &#x1f349;Naive UI 是一个轻量、现代化且易于使用的 Vue 3 UI 组件库&#xff0c;它提供了一组简洁、易用且功能强大的组件&#xff0c;旨在为开发者提供更高效的开发体验&#xff0c;特别是对于构建现代化的 web 应用程序。…

WPF DataGrid 列隐藏

Window节点加上下面的 <Window.Resources><FrameworkElement x:Key"ProxyElement" DataContext"{Binding}" /></Window.Resources>然后随便加一个隐藏控件 <ContentControl Content"{StaticResource ProxyElement}" Visi…

【Gitlab】CICD使用minio作为分布式缓存

1、安装minio 下载适合自己系统版本的安装文件https://dl.min.io/server/minio/release/windows-amd64/ yum install xxx.rpm 2、配置/etc/profile export MINIO_ACCESS_KEYroot [ui登录账号] export MINIO_SECRET_KEYminioDev001 [ui登录密码] export MINIO_OPTS"…

用到动态库的程序运行过程

当我们写好了一段代码然后编译运行后会生成可执行文件&#xff0c;该文件会存在磁盘的当前目录下&#xff0c;而当我们开始运行这段程序时&#xff0c;操作系统&#xff08;加载器&#xff09;需要将其从磁盘加载进内存然后执行相关操作&#xff0c;而对于用到动态库的程序&…

ansible自动化运维(一)配置主机清单

目录 一、介绍 1.1了解自动化运维 1.2 ansible简介 1.3 ansible自动化运维的优势 1.4 ansible架构图 二、部署ansible 2.1 基本参数 2.2 Ansible帮助命令 2.3 配置主机清单 2.3.1 查看ansible的所有配置文件 2.3.2 /etc/ansible/ansible.cfg常用配置选项 2.3.3 ssh密…