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

【Linux学习笔记】日志器与线程池设计

【Linux学习笔记】日志器与线程池设计

在这里插入图片描述

🔥个人主页大白的编程日记

🔥专栏Linux学习笔记


文章目录

  • 【Linux学习笔记】日志器与线程池设计
    • 前言
  • 3-1日志与策略模式
    • 3-1-1 什么是设计模式
    • 3-1-2 日志认识
    • 3-1-3 Log.hpp
    • 3-1-4 策略模式
  • 3-2 线程池设计
    • 3-2-1 线程池:
    • 3-2-2 线程池的应用场景:
    • 3-2-3 线程池的种类
    • 3-2-4 ThreadPool.hpp
    • 后言

前言

哈喽,各位小伙伴大家好!上期我们讲了阻塞队列和环形队列 今天我们讲的是日志器与线程池设计。话不多说,我们进入正题!向大厂冲锋!
在这里插入图片描述

  1. 线程池

下面开始,我们结合我们之前所做的所有封装,进行一个线程池的设计。在写之前,我们要做如下准备

  • 准备线程的封装
  • 准备锁和条件变量的封装
  • 引入日志,对线程进行封装

3-1日志与策略模式

3-1-1 什么是设计模式

IT行业这么火,涌入的人很多.俗话说林子大了啥鸟都有.大佬和菜鸡们两极分化的越来越严重.为了让菜鸡们不太拖大佬的后腿,于是大佬们针对一些经典的常见的场景,给定了一些对应的解决方案,这个就是设计模式

3-1-2 日志认识

计算机中的日志是记录系统和软件运行中发生事件的文件,主要作用是监控运行状态、记录异常信息,帮助快速定位问题并支持程序员进行问题修复。它是系统维护、故障排查和安全管理的重要工具。

日志格式以下几个指标是必须得有的

时间戳

  • 日志等级
  • 日志内容

以下几个指标是可选的

  • 文件名行号
  • 进程,线程相关id信息等

日志有现成的解决方案,如:spdlog、glog、Boost.Log、Log4cxx等等,我们依旧采用自定义日志的方式。

这里我们采用设计模式-策略模式来进行日志的设计,具体策略模式介绍,详情看代码和课程。

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

1 [可读性很好的时间] [日志等级] [进程pid] [打印对应日志的文件名][行号] - 消息内容,支持可变参数

2 [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] - hello world  
3 [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [17] - hello world  
4 [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [18] - hello world  
5 [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [20] - hello world  
6 [2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [21] - hello world  
7 [2024-08-04 12:27:03] [WARNING] [202938] [main.cc] [23] - hello world

3-1-3 Log.hpp

因为我们的日志可以想显示器打印也可以向日志打印
所以这里我们采用策略模式 定义一个基类和一个刷新函数虚函数
由日志类和显示器类重写虚函数

#ifndef __LOG_HPP__
#define __LOG_HPP__#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include "Mutex.hpp"
using namespace std;namespace LogModule
{using namespace MutexModule; // 使用互斥锁模块const std::string gsep = "\r\n"; // 全局分隔符// 策略模式,C++多态特性// 2. 刷新策略 a: 显示器打印 b:向指定的文件写入//  刷新策略基类class LogStrategy{public:virtual void SyncLog(const std::string &message) = 0; // 纯虚函数,定义日志刷新接口};// 显示器刷新日志类 重写虚函数class ConsoleLogstrategy : public LogStrategy{public:virtual void SyncLog(const std::string &message) // 重写日志刷新函数{LockGuard guard(_mutex); // 使用互斥锁保护cout << message << endl; // 在控制台打印日志}private:Mutex _mutex; // 互斥锁,防止多线程冲突};// 1. 形成日志等级enum class LogLevel // 日志等级枚举{DEBUG,INFO,WARNING,ERROR,FATAL};// 把日志登记转为字符串string Level2str(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 "UNKNOWN";}}// 把当前时间戳转为固定格式的字符串std::string GetTimeStamp() // 获取当前时间戳{time_t curr = time(nullptr); // 获取当前时间struct tm curr_tm;localtime_r(&curr, &curr_tm); // 转换为本地时间char timebuff[128];snprintf(timebuff, 128, "%4d-%02d-%02d-%02d-%02d-%02d", // 格式化时间curr_tm.tm_year + 1900,curr_tm.tm_mon + 1,curr_tm.tm_mday,curr_tm.tm_hour,curr_tm.tm_min,curr_tm.tm_sec);return timebuff;}// 文件打印日志的策略 : 子类const std::string defaultpath = "./log"; // 默认日志文件夹路径const std::string defaultfile = "my.log"; // 默认日志文件名class FileLogStrategy : public LogStrategy // 文件日志策略类{public:FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile) // 构造函数: _path(path), _file(file){if (std::filesystem::exists(_path)) // 检查路径是否存在{return;}try{std::filesystem::create_directories(_path); // 创建日志文件夹}catch (const std::filesystem::filesystem_error &e) // 捕获异常{cout << e.what() << endl;}}// 文件刷新函数 打开文件流输入virtual void SyncLog(const std::string &message) // 重写日志刷新函数{LockGuard guard(_mutex); // 使用互斥锁保护string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // 构造文件路径ofstream of(filename, ios::app); // 打开文件流if (!of.is_open()) // 检查文件是否打开成功{return;}of << message << endl; // 写入日志of.close(); // 关闭文件流}private:string _path; // 文件目录string _file; // 文件路径Mutex _mutex; // 互斥锁,防止多线程冲突};// 日志类class Logger{public:Logger() // 构造函数{EnableConsoleLogStrategy(); // 默认启用控制台日志策略}void EnableFileLogStrategy() // 启用文件日志策略{_fflush_strategy = make_unique<FileLogStrategy>(); // 创建文件日志策略对象}void EnableConsoleLogStrategy() // 启用控制台日志策略{_fflush_strategy = make_unique<ConsoleLogstrategy>(); // 创建控制台日志策略对象}// 模版日志信息内部类 通过内部类拿到日志类class LogMessage // 日志消息类{public:LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger) // 构造函数: _level(level),_line_number(line_number),_src_name(src_name),_logger(logger),_pid(getpid()), // 获取当前进程ID_curr_time(GetTimeStamp()) // 获取当前时间戳{stringstream ss;ss << "[" << _curr_time << "] " // 构造日志头<< "[" << Level2str(_level) << "] "<< "[" << _pid << "] "<< "[" << _src_name << "] "<< "[" << _line_number << "] "<< "- ";_loginfo = ss.str(); // 保存日志头}// 返回临时对象 形成连续留提取template <typename T>LogMessage &operator<<(const T &info) // 重载<<操作符{stringstream ss;ss << info; // 将信息转换为字符串_loginfo += ss.str(); // 追加到日志信息return *this; // 返回当前对象,支持链式调用}~LogMessage() // 析构函数{if (_logger._fflush_strategy) // 检查是否有刷新策略{_logger._fflush_strategy->SyncLog(_loginfo); // 调用刷新策略输出日志}}private:string _curr_time; // 当前时间LogLevel _level; // 日志等级pid_t _pid; // 进程IDstd::string _src_name; // 文件名int _line_number; // 行号std::string _loginfo; // 完整的日志信息Logger &_logger; // 引用日志类对象};// 这里故意写成返回临时对象LogMessage operator()(LogLevel level, std::string name, int line) // 日志类重载()操作符{return LogMessage(level, name, line, *this); // 返回日志消息对象}private:// 指向策略类的智能指针unique_ptr<LogStrategy> _fflush_strategy; // 日志刷新策略对象};// 全局日志对象Logger logger;// 使用宏,简化用户操作,获取文件名和行号#define LOG(level) logger(level, __FILE__, __LINE__) // 定义日志宏#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy() // 启用控制台日志策略宏#define Enable_File_Log_Strategy() logger.EnableFileLogStrategy() // 启用文件日志策略宏
}
#endif

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3-1-4 策略模式

策略模式(Strategy
Pattern)是一种行为型设计模式,用于定义一系列的算法,将每个算法封装起来,并使它们可以互换。策略模式让算法的变化独立于使用算法的客户。它提供了一种方法,使得算法可以在运行时动态地切换,而不需要修改客户端代码。

核心概念

  • 策略接口(Strategy Interface): 定义了所有支持的操作的公共接口。客户端使用这个接口来调用具体的算法。

  • 具体策略(Concrete Strategies): 实现了策略接口的具体算法。每个具体策略类都提供了一种不同的算法实现

  • 上下文(Context): 维护一个对策略对象的引用,允许客户端调用策略定义的接口。上下文可以动态地切换策略对象,从而改变其行为。

#include <iostream>
#include <vector>
#include <algorithm>// 策略接口
class SortStrategy
{
public:virtual void sort(std::vector<int> &data) = 0;virtual ~SortStrategy() {}
};
// 冒泡排序策略
class BubbleSortStrategy : public SortStrategy
{
public:void sort(std::vector<int> &data) override{for (size_t i = 0; i < data.size(); ++i){for (size_t j = 0; j < data.size() - i - 1; ++j){if (data[j] > data[j + 1]){std::swap(data[j], data[j + 1]);}}}}
};// 快速排序策略
class QuickSortStrategy : public SortStrategy
{
public:void sort(std::vector<int> &data) override{std::sort(data.begin(), data.end());}
};
// 上下文类
class SortContext
{
public:SortContext(SortStrategy *strategy) : _strategy(strategy) {}void setStrategy(SortStrategy *strategy){_strategy = strategy;}void sort(std::vector<int> &data){_strategy->sort(data);}private:SortStrategy *_strategy;
};
int main()
{std::vector<int> data = {5, 2, 9, 1, 5, 6};// 使用冒泡排序策略SortContext context(new BubbleSortStrategy());context.sort(data);for (int num : data){std::cout << num << " ";}std::cout << std::endl;// 切换到快速排序策略context.setStrategy(new QuickSortStrategy());context.sort(data);for (int num : data){std::cout << num << " ";}std::cout << std::endl;return 0;
}

3-2 线程池设计

3-2-1 线程池:

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

3-2-2 线程池的应用场景:

  • 需要大量的线程来完成任务,且完成任务的时间比较短。比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

3-2-3 线程池的种类

  • a. 创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执行任务对象中的任务接口
  • b. 浮动线程池,其他同上

此处,我们选择固定线程个数的线程池。
在这里插入图片描述

3-2-4 ThreadPool.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"
using namespace std;namespace ThreadPoolModule
{using namespace ThreadModule;using namespace LogModule;using namespace CondModule;using namespace MutexModule;static const int gnum = 5; // 默认线程池大小template <typename T>class ThreadPool{public:// 初始化线程池ThreadPool(int num = gnum) // 构造函数,初始化线程池: _num(num), _isrunning(false), _sleepernum(0){for (int i = 0; i < _num; i++){_threads.emplace_back( // 创建线程对象[this](){HandlerTask(); // 线程执行的任务处理函数});}}// 往任务队列push任务bool Enqueue(const T &in) // 将任务加入任务队列{if (_isrunning) // 检查线程池是否在运行{LockGuard guard(_mutex); // 加锁,保护任务队列_taskq.push(in); // 将任务加入队列if (_threads.size() == _sleepernum) // 如果所有线程都在休眠{WakeUponce(); // 唤醒一个线程}return true;}return false;}// 启动线程池void Start() // 启动线程池{if (_isrunning) // 如果线程池已经在运行,直接返回{return;}_isrunning = true; // 设置线程池为运行状态for (auto &x : _threads) // 遍历线程池中的线程{x.Start(); // 启动每个线程LOG(LogLevel::INFO) << "start new thread success: " << x.Name(); // 日志记录}}void HandlerTask() // 线程任务处理函数{char name[128];pthread_getname_np(pthread_self(), name, sizeof(name)); // 获取当前线程名称while (true)//while循环防止虚伪唤醒{T t;{LockGuard guard(_mutex); // 加锁,保护任务队列// 如果任务队列为空并且线程池在运行状态,线程进入等待while (_taskq.empty() && _isrunning){_sleepernum++; // 增加休眠线程计数_cond.Wait(_mutex); // 等待条件变量_sleepernum--; // 减少休眠线程计数}// 如果线程池不运行并且任务队列为空,线程退出if (!_isrunning && _taskq.empty()){LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";break;}t = _taskq.front(); // 获取任务_taskq.pop(); // 弹出任务}t(); // 执行任务}}static ThreadPool<T> *GetInstance() // 获取线程池单例{if (inc == nullptr) // 检查单例是否已创建{LockGuard lockguard(_lock); // 加锁,防止多线程并发创建LOG(LogLevel::DEBUG) << "获取单例....";if (inc == nullptr) // 双重检查锁定{LOG(LogLevel::DEBUG) << "首次使用单例, 创建之....";inc = new ThreadPool<T>; // 创建线程池单例inc->Start(); // 启动线程池}}return inc;}void WakeUponce() // 唤醒一个休眠线程{_cond.Signal(); // 信号量通知一个线程LOG(LogLevel::INFO) << "唤醒一个休眠线程";}void WakeUpAllThread() // 唤醒所有休眠线程{LockGuard guard(_mutex); // 加锁,保护条件变量if (_sleepernum > 0) // 如果有休眠线程{_cond.Brodcast(); // 广播通知所有线程}LOG(LogLevel::INFO) << "唤醒所有的休眠线程";}void Stop() // 停止线程池{if (!_isrunning) // 如果线程池已经停止,直接返回{return;}_isrunning = false; // 设置线程池为停止状态WakeUpAllThread(); // 唤醒所有线程}void Join() // 等待线程池中的所有线程完成{for (auto x : _threads) // 遍历线程池中的线程{x.Join(); // 等待每个线程完成}}private:std::vector<Thread> _threads; // 线程池,存储线程对象int _num; // 线程池中线程的数量std::queue<T> _taskq; // 任务队列,存储任务Cond _cond; // 条件变量,用于线程同步Mutex _mutex; // 互斥锁,保护任务队列和条件变量bool _isrunning; // 线程池是否在运行int _sleepernum; // 休眠线程的数量static ThreadPool<T> *inc; // 线程池单例指针static Mutex _lock; // 用于单例创建的互斥锁};template <typename T>ThreadPool<T> *ThreadPool<T>::inc = nullptr; // 初始化单例指针template <typename T>Mutex ThreadPool<T>::_lock; // 初始化单例锁
}

线程退出:只有线程池的任务取完或者线程不运行才会退出
也就是说只要还有任务没取完 或者线程正在运行就不退出
但是有可能有线程还在休眠 所以我们应该唤醒所有的休眠线程 把所有的线程设置为退出状态

后言

这就是日志器与线程池设计。大家自己好好消化!今天就分享到这! 感谢各位的耐心垂阅!咱们下期见!拜拜~

http://www.dtcms.com/a/528330.html

相关文章:

  • 【Linux系统编程】编辑器vim
  • 鸿蒙ArkTS入门教程:小白实战“易经”Demo,详解@State、@Prop与List组件
  • 扩散模型与UNet融合的创新路径
  • 从入门到精通的鸿蒙学习之路——基于鸿蒙6.0时代的生态趋势与实战路径
  • 704.力扣LeetCode_二分查找
  • 如何做企业网站宣传wordpress 显示空白
  • 机器学习库的线性回归预测
  • 旅游网站开发研究背景北京欢迎您
  • 做网站要学什么东西企业网站运维
  • Orleans Grain Directory 系统综合分析文档
  • 从PN结到GPIO工作模式
  • 面向社科研究者:用深度学习做因果推断(三)
  • 深度学习-MNIST手写数字识别(MLP)
  • K8s 静态持久化存储详解
  • wordpress seo 能提高网站速度吗
  • GitHub等平台形成的开源文化正在重塑特尔恩恩
  • 追根索源:换不同的词嵌入(词向量生成方式不同,但词与词关系接近),会出现什么结果?
  • 视频与音频碰撞,谷歌 Veo 3.1,生成“有声电影”,人物对话超震撼
  • 【PID】基本PID控制 chaprt1 学习笔记
  • 【大语言模型 103】推理服务监控:性能指标、故障诊断与自动恢复实战
  • 网站广东海外建设集团有限公司做网站工资多钱
  • Julia 字符串处理指南
  • volatile关键词探秘:从咖啡厅的诡异订单到CPU缓存之谜
  • 嵌入式Lua脚本编程核心概念
  • VScode开发环境搭建(本文为个人学习笔记,内容整理自哔哩哔哩UP主【非学者勿扰】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
  • 基于springboot的车辆管理系统设计与实现
  • WPF GroupBox 淡入淡出
  • Dify从入门到精通 第33天 基于GPT-4V构建图片描述生成器与视觉问答机器人
  • 网页制作与网站建设实战教程视频网站一般用什么数据库
  • React 05