当前位置: 首页 > news >正文

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

🌈 个人主页: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);  // 时间戳,获取可读性较强的时间信息Schar buffer[1024];// bugsnprintf(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.txtstd::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;                 // 进程pidstd::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】线程池设计 + 策略模式 的理解,觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~😉
在这里插入图片描述

相关文章:

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

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; 线程池 1-1 ⽇志与策略模式1-2 线程池设计1-3 线程安全的单例模式1-3-1 什么是单例模式1-3-2 单例模式的特点1-3-3 饿汉实现⽅式和懒汉实现⽅式1-3-4 饿汉…...

网络原理(一):应用层自定义协议的信息组织格式 HTTP 前置知识

目录 1. 应用层 2. 自定义协议 2.1 根据需求 > 明确传输信息 2.2 约定好信息组织的格式 2.2.1 行文本 2.2.2 xml 2.2.3 json 2.2.4 protobuf 3. HTTP 协议 3.1 特点 4. 抓包工具 1. 应用层 在前面的博客中, 我们了解了 TCP/IP 五层协议模型: 应用层传输层网络层…...

Python-链表数据结构学习(1)

一、什么是链表数据&#xff1f; 链表是一种通过指针串联在一起的数据结构&#xff0c;每个节点由2部分组成&#xff0c;一个是数据域&#xff0c;一个是指针域&#xff08;存放下一个节点的指针&#xff09;。最后一个节点的指针域指向null&#xff08;空指针的意思&#xff0…...

性能优化经验:关闭 SWAP 分区

关闭 SWAP 分区&#xff0c;特别是在性能敏感场景&#xff08;如 Elasticsearch 服务&#xff09;中&#xff0c;主要与 SWAP 的工作机制和对应用性能的影响有关。以下是详细原因&#xff1a; 1. SWAP 的工作机制导致高延迟 SWAP 是什么&#xff1a; SWAP 分区是系统将物理内存…...

SpringBoot小知识(2):日志

日志是开发项目中非常重要的一个环节&#xff0c;它是程序员在检查程序运行的手段之一。 1.日志的基础操作 1.1 日志的作用 编程期调试代码运营期记录信息&#xff1a; * 记录日常运营重要信息(峰值流量、平均响应时长……) * 记录应用报错信息(错误堆栈) * 记录运维过程数据(…...

java虚拟机——jvm是怎么去找垃圾对象的

JVM&#xff08;Java虚拟机&#xff09;通过特定的算法和机制来查找和识别垃圾对象&#xff0c;以便进行垃圾回收。以下是JVM查找垃圾对象的主要方法和步骤&#xff1a; 一、可达性分析法 JVM使用可达性分析法来识别垃圾对象。这种方法从一组称为“GC Roots”的对象作为起始点…...

Macos远程连接Linux桌面教程;Ubuntu配置远程桌面;Mac端远程登陆Linux桌面;可能出现的问题

文章目录 1. Ubuntu配置远程桌面2. Mac端远程登陆Linux桌面3. 可能出现的问题1.您用来登录计算机的密码与登录密钥环里的密码不再匹配2. 找不到org->gnome->desktop->remote-access 1. Ubuntu配置远程桌面 打开设置->共享->屏幕共享。勾选允许连接控制屏幕&…...

hadoop_HA高可用

秒懂HA HA概述HDFS-HA工作机制工作要点元数据同步参数配置手动故障转移自动故障转移工作机制相关命令 YARN-HA参数配置自动故障转移机制相关命令 附录Zookeeper详解 HA概述 H(high)A(avilable)&#xff1a; 高可用&#xff0c;意味着必须有容错机制&#xff0c;不能因为集群故障…...

【MySQL】MySQL中的函数之JSON_ARRAY_APPEND

在 MySQL 8.0 及更高版本中&#xff0c;JSON_ARRAY_APPEND() 函数用于在 JSON 数组的指定位置追加一个或多个值。这个函数非常有用&#xff0c;特别是在你需要在 JSON 数组的末尾或特定位置添加新的元素时。 基本语法 JSON_ARRAY_APPEND(json_doc, path, val[, path, val] ..…...

torch.is_nonzero(input)

torch.is_nonzero(input) input: 输入张量 若输入是 不等于零的单元素张量 则返回True&#xff0c;否则返回False 不等于零的单元素张量&#xff1a;torch.tensor([0.]) 或 torch.tensor([0]) 或 torch.tensor([False])单元素张量: 只有一个数 的张量 import torch print(t…...

文本搜索程序(Qt)

头文件 #ifndef TEXTFINDER_H #define TEXTFINDER_H#include <QWidget> #include <QFileDialog> #include <QFile> #include <QTextEdit> #include <QLineEdit> #include <QTextStream> #include <QPushButton> #include <QMess…...

使用 Python 剪辑视频的播放速度

要使用 Python 调整视频的播放速度&#xff0c;可以利用 moviepy 库中的 fx&#xff08;特效&#xff09;模块来实现这一功能。通过 moviepy.editor 中的 VideoFileClip 类和 fx.speedx 函数&#xff0c;可以轻松地调整视频的播放速度。 安装 moviepy 首先&#xff0c;确保已…...

深入理解计算机系统,源码到可执行文件翻译过程:预处理、编译,汇编和链接

1.前言 从一个高级语言到可执行程序&#xff0c;要经过预处理、编译&#xff0c;汇编和链接四个过程。大家可以思考下&#xff0c;为什么要有这样的过程&#xff1f; 我们学习计算机之处&#xff0c;就应该了解到&#xff0c;计算机能够识别的只有二进制语言&#xff08;这是…...

Linux开发者的CI/CD(11)jenkins变量

文章目录 1. **环境变量 (Environment Variables)**常见的环境变量:示例:2. **构建参数 (Build Parameters)**常见的构建参数类型:示例:3 **在 `stages` 块内定义局部变量**示例:使用 `script` 步骤定义局部变量4 变量引用陷阱在 Jenkins 中,变量是自动化流程中非常重要的…...

深度学习视频编解码开源项目介绍【持续更新】

DVC (Deep Video Compression) 介绍&#xff1a;DVC (Deep Video Compression) 是一个基于深度学习的视频压缩框架&#xff0c;它的目标是通过深度神经网络来提高视频编码的效率&#xff0c;并降低比特率&#xff0c;同时尽可能保持视频质量。DVC 是一个端到端的神经网络模型&…...

Canva迁移策略深度解析:应对每日5000万素材增长,从MySQL到DynamoDB的蜕变

随着数字化设计的蓬勃发展&#xff0c;Canva作为一款备受欢迎的在线设计平台&#xff0c;面临着日益增长的用户生成内容挑战。每天&#xff0c;平台上新增的素材数量高达5000万&#xff0c;这对数据库系统提出了前所未有的要求。为了应对这一挑战&#xff0c;Canva决定对其数据…...

nacos常见面试题(2024)

nacos永久实例与临时实例区别 nacos实例有2种&#xff0c;分别为临时实例&#xff08;一般业务服务是临时的&#xff09;和永久实例&#xff08;如mysql、redis这种运维服务需要实时看到状态的设置为永久实例&#xff09;。 临时实例只会缓存到服务注册列表中&#xff0c;下线…...

68000汇编实战01-编程基础

文章目录 简介产生背景应用领域 语言学习EASy68K帮助文档IDE使用 编程语言commentslabels开始标签指令标签位置标签 opcode 操作码常用操作码数据传送算术运算逻辑运算控制流分支跳转地址跳转子程序跳转 位操作比较堆栈操作 IO操作码其他操作码 directives 指令DC指令EQU 指令S…...

你的网站真的安全吗?如何防止网站被攻击?

你的网站被黑客攻击过&#xff0c;很可能不止一次&#xff01; 这可不是危言耸听。微软最近发布了《2024 年微软数字防御报告》&#xff0c;报告中写到&#xff1a;“Windows 用户每天面临超过 6 亿次网络犯罪和国家级别的攻击&#xff0c;涵盖了从勒索软件到网络钓鱼再到身份…...

UE5 材质编辑器CheapContrast 节点

在 Unreal Engine 材质编辑器中&#xff0c;CheapContrast 节点是一个非常实用的节点&#xff0c;主要用于对图像或纹理的 对比度 进行调整&#xff0c;且执行效率较高&#xff0c;适合在性能要求较高的场景中使用。 CheapContrast 节点的作用 CheapContrast 节点通过调整输入…...

健身房小程序服务渠道开展

健身不单单是锻炼身体、保持身材&#xff0c;也是一种社交方式&#xff0c;城市里门店不少&#xff0c;每家都有一定流量和老客&#xff0c;但仅靠传统线下拉客/自然流量前往和线上朋友圈、短视频发硬广等方式还不够。 商家需要找到更多潜在目标客户&#xff0c;而消费者也对门…...

Java基础面试题08:Java中Exception和Error有什么区别?

在Java中&#xff0c;Exception 和 Error 是异常处理体系的两大核心概念。要理解它们的区别和应用&#xff0c;咱们可以逐步剖析。 Exception和Error的基础区别 共同点&#xff1a; 两者都继承自 Throwable 类&#xff0c;只有 Throwable 类型的实例才能被 throw 或 catch。 区…...

什么是axios?怎么使用axios封装Ajax?

学习目标 什么是axios怎么使用axios封装Ajax该如何使用Axios 封装 XHR 请求 什么是axios Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;它可以在浏览器和 Node.js 环境中使用。Axios 提供了简单易用的 API&#xff0c;用于执行各种 HTTP 请求操作&#xff0c;如 GET、P…...

Web前端学习_CSS盒子模型

content padding border margin <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS盒子模型</title><style></style> </head> <body> <div class"demo&quo…...

JAVA项目-------医院挂号系统

1&#xff0c;项目目的 1、科室管理&#xff1a;新增科室&#xff0c;删除科室&#xff08;如果有医生在&#xff0c;则不能删除该科室&#xff09;&#xff0c;修改科室。 2、医生管理&#xff1a;录入医生信息&#xff0c;以及科室信息。修改医生信息&#xff08;主要是修改…...

[工具分享] 根据Excel数据根据Word文档模板,批量创建生成Word文档并重命名,方便快速查找打印

前几天交楼的小姐姐要多份Word文档合同打印给客户&#xff0c;那么100份就需要修改100次 上面好多都是模板的制式文件&#xff0c;里面的部分数据都是要根据实际值来变动的&#xff0c; 那么有没有快速的方法来操作呢&#xff0c;还是只能一个个手动的改&#xff0c;又容易出…...

Redis的管道操作

在现代应用程序中&#xff0c;Redis作为一种高性能的内存数据库&#xff0c;被广泛用于缓存、消息队列、实时分析等场景。为了进一步提高Redis的性能&#xff0c;Redis提供了管道&#xff08;Pipeline&#xff09;操作&#xff0c;允许客户端将多个命令一次性发送到服务器&…...

IT监控 | Oracle云监控全解析

Oracle云(Oracle Cloud)是Oracle公司提供的云服务平台&#xff0c;涵盖了IaaS、PaaS、SaaS和DaaS&#xff0c;支持企业在云中构建、部署、集成和扩展应用&#xff0c;为企业提供了管理服务器、应用程序、存储、网络和数据中心的全面控制能力。 跟踪Oracle云基础设施的关键组件将…...

前端面试题-1(详解事件循环)

1.了解浏览器的进程模型 1.什么是进程&#xff1f; 程序运行需要有它自己专属的内存空间&#xff0c;可以把这块内存空间简单的理解为进程 每个应用至少有一个进程&#xff0c;进程之间相互独立&#xff0c;即使要通信&#xff0c;也需要双方同意。 2.什么是线程&#xff1f…...

Redis(5):哨兵

一、作用和架构 1. 作用 在介绍哨兵之前&#xff0c;首先从宏观角度回顾一下Redis实现高可用相关的技术。它们包括&#xff1a;持久化、复制、哨兵和集群&#xff0c;其主要作用和解决的问题是&#xff1a; 1&#xff09;持久化&#xff1a;持久化是最简单的高可用方法(有时甚…...

wordpress国人主题/百度收录技术

背景分析 从视频智能化相关技术研发开始计算&#xff0c;智能化在视频行业内已经发展了十余年&#xff0c;但是视频行业的智能化应用一直没有达到预期。目前视频智能化主要的表现还是集中在前端设备摄像机产品的某些智能功能&#xff0c;以及一些配备智能分析的NVR/DVR和后端的…...

自己有服务器和域名怎么做网站/seo入门基础教程

在对数据字段进行分类管理时&#xff0c;利用动态树折叠数据是一个很好的方法&#xff0c;也就是点击数据前面的加号才展开对应下面的数据&#xff0c;如下图。那这样的效果在制作报表时该如何实现呢&#xff1f; 下面以报表工具FineReport为例介绍。 思路&#xff1a; 通过将模…...

企业做网站用dedeCMS免费吗/广州网站seo

http://www.runoob.com/design-pattern/factory-pattern.html 菜鸟教程 1.工厂模式&#xff1a; 个人感觉就是利用 多态&#xff08;例如动物&#xff0c;叫声那个例子&#xff09;工厂&#xff08;创建各个类的实列的自定义工厂&#xff09;测试类&#xff08;给定一种动物&am…...

南宁网站建设nnxun/厦门推广平台较好的

Description 给出N个长度不超过5000的只含数字的字符串&#xff0c;你需要回答M次形如i j的询问&#xff0c;对于每次询问用一行输出一个整数表示第i个字符串和第j个字符的最长公共前缀的长度。比如两个字符串分别为201212和201112&#xff0c;"2"、"20"和…...

在dw里如何做网站/网络营销师报名官网

基于娱乐开源commons-email-1.x包之后&#xff0c;花了点时间重新整理了一下代码和GUI设计&#xff0c;只想利用已有的组件搭起一个自己的邮件客户端&#xff0c;如果在往高出走点&#xff0c;也可以弄一个邮件服务器来。 1.开发目的 简单&#xff0c;甚至单纯的可怕。造重复的…...

网站页面太多是否做静态/百度推广代理

druid多数据源 AOP注解切换数据源 GITEE代码...