news 2026/7/17 6:20:26

日志与线程池

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
日志与线程池
@bit::Shadow
✧(≖ ◡ ≖✿

目录

日志

日志介绍

日志的中心实现

5个亮点

0.策略模式

1.gsep(global seperator)分隔符(兼容性极好缓冲区刷新行为)

2.命名空间+宏定义用户态接口

3.重载<<递推返回

4.enum class强枚举类型支持

开发顺序

线程池

线程封装

制作线程池

框架图

​编辑

ThreadPool(int num)

void HandlerTask()

std::queue _taskq

void Stop()

宏的兼容bug


日志

日志介绍

日志是软件系统中记录程序运行状态、事件和错误信息的机制,是调试、监控和问题排查的核心工具。

日志是什么?

日志是记录程序在运行过程中产生的结构化/半结构化的文本记录,用于描述系统在特定时间点发生了什么。

日志的输出常包含,时间(精确到秒),日志类型(INFO"信息",WARN"警告"),产生位置,详细信息。

日志的中心实现

仅实现日志中关键片段

namespace LogModule { //枚举 gsep operator() operator<< 宏LOG(level)及另外两个 enum class LogLevel { DEBUG, INFO, WARN, ERROR, FATAL }; const std::string gsep = "\r\n";//换行平台兼容性考虑 //Win下记事本显示兼容 //Linux 下 \r 被忽略,只识别 \n,所以也兼容 //HTTP兼容 class LogMessage //设计外部类 { public: LogMessage() = default; LogMessage(LogLevel level, std::string name, int line) : _level(level), _name(name), _line(line) { ; // #define自动 } ~LogMessage() {} //支持递推 // Log(LogLevel::DEBUG) << "hell world" << "XXXX" << 3.14 << 1234 template<typename T> LogMessage operator<<(T t) { std::cout << t; return LogMessage();//返回临时对象(右值属性)。---不能使用拷贝接受 } class Log { // Log(LogLevel::DEBUG) << "这是一条日志"; 末尾自动换行无需std::endl // 这是一条日志 LogMessage operator()(LogLevel level, std::string name, int line) { return LogMessage(level, name, line); } }; private: LogLevel _level; std::string _name; int _line; }; LogMessage Log;//用户端使用接口! //宏替换 #define Log(lv) LogMessage(lv, __FILE__, __LINE__) }

5个亮点

0.策略模式

是开发基于的框架,不是开发的顺序依赖。

1.gsep(global seperator)分隔符(兼容性极好缓冲区刷新行为)
//const char* gsep = "\r\n"; const std::string gsep = "\r\n"; //Win下记事本显示兼容 //Linux 下 \r被忽略,只识别 \n,所以也兼容 //HTTP兼容

优化点:"std::string"为什么?

利用string的自动扩容管理。
支持接口类型很多,更加灵活。

2.命名空间+宏定义用户态接口
#define Log(lv) Logmessage(lv, __FILE__, __LINE__)

注意点:1.宏定义不要随意加注释内容。 易造成宏混乱
2.lv与lv的传递兼容。 "宏函数"
3.预处理宏__FILE____LINE__

3.重载<<递推返回
template<class T> //LogMessage &? LogMessage operator<<(T t) { //std::stringstream ss; //1.待纳入I/O流——处理换行刷新 return *this;拷贝返回接收 std::cout << t; //2.简单实现 return LogMessage();!!! }

注意点:可以使用拷贝返回吗?
答:不可以!因为LogMessage()作为临时对象返回的右值属性!!!

4.enum class强枚举类型支持
enum class LogLevel { DEBUG, INFO, WARN, ERROR, FATAL };

开发顺序

第1步:核心基础设施

第2步:策略接口和具体策略

第3步:日志消息类(核心)

第4步:日志器(组装)

第5步:宏封装和全局对象

待🔗C++进阶知识4.0

线程池

线程封装

gitee

#pragma once #include<string> #include<pthread.h> #include<functional> #include<atomic> // void* str = static_cast<void*>(s); //// 1.const char*不能直接转换为void*需要先使用const_cast去除const属性 #define RETERROR(s) \ do { \ void* str = static_cast<void*>(const_cast<char*>(s));\ pthread_exit(str);\ }while(0) ///2.使用pthread_exit而不是exit()进程退出 // static uint32_t number = 3; namespace ThreadModule { //原子计数器操作 static std::atomic<uint32_t> number{1}; ///3. static uint32_t number = 1;//1.生命周期全局。2.高平台兼容性。 using func_t = std::function<void()>;////4.无参数无返回值的"函数包装器" class Thread { private: ///5. 函数只能被内部访问无法被当做参数 static void* Route(void* arg)//static { Thread* self = static_cast<Thread*>(arg); pthread_setname_np(self->pd, self->_name.c_str()); ///6. // self->_func;//回调新线程_func() self->_func();//回调新线程 // return (void*)(self->_name.c_str());//此处是主进程 return nullptr; } bool EnableDetach() { if (!_isRunning) return false; // detach线程状态变为unjionable,线程退出自动被主线程回收无需再join // pthread_detach(pd);//把新线程设置为分离了 _isDetach = false; return true; } bool EnableRunning() { _isRunning = true; return true; } public: Thread(func_t func) :_isRunning(false ) ,_isDetach(false) ,_func(func) ,res(nullptr) { //命名 _name = "thread-" + std::to_string(number++); } ~Thread() { //7. if (_isDetach) pthread_exit(0);若对象在栈上会导致未定义行为 //若在堆上则不会被detach处理,会出错 } void Detach() { if (!_isRunning || _isDetach) return; EnableDetach(); } bool Start()//自定义设置新线程名字 { if(_isRunning) return false; int n = pthread_create(&pd, nullptr, Route, (void*)this); if(n) RETERROR("pthread_create"); EnableRunning(); return true; } bool Stop() { //停止"cancel" if(_isRunning) { int n = pthread_cancel(pd); if(n) RETERROR("cancel failed\n"); } } void Join() { //等待新线程 if (!_isRunning || _isDetach) return; int n = pthread_join(pd, &res); if (n) RETERROR("join failed\n"); } private: pthread_t pd; std::string _name; bool _isRunning; bool _isDetach; func_t _func; void* res; }; }

制作线程池

框架图

“外部任务分配”模块暂不实现。

ThreadPool(int num)

构造函数:指定申请线程个数。

实现原理:

vector维护vector<Thread> _threads; // 线程群,_num个线程。
void HandlerTask(); 通过封装的线程构造参数,被同等地分配到各个线程。

ThreadPool(int num) : _num(num) { // 多线程创建与支持 for (int i = 0; i < _num; i++) { // emplace_back()纳入 _threads.emplace_back([this]() { HandlerTask(); // lambda表达式返回值类型由编译器自动推导 }); // 纳入的类型取决于构造函数的参 // printf("1\n");//lambda仅是纳入并非直接调用 } // 传过去的this调用同一个函数?这不就重入了? // 要的就是重入,vector下重入 }

void HandlerTask()

维护任务队列,支持并发。

原理:
1.队列模板元编程。
2.并发编程模板

主操作——主操作边界条件(像:满 空 打开失败等等)—— (考虑并发问题)加锁/解锁——外部依赖(像:EQueue线程-考虑Pop与线程的通信"唤醒条件")——内部依赖(线程池的单例模式:GetInstance() { if(inc == nullptr) //..... inc = newThreadPool<T>()“内部调用构造函数”};单if条件下——若快进程先new,慢进程后判断nullptr。可能导致慢进程误判仍为空,所以需要双重if。

// 区别线程分发任务接口 void HandlerTask() // 对应using func_t = // std::function(void());代表无参数,无返回值的函数包装器 { // 处理任务。 // 线程名字(无并发问题) char name[128] = {0}; pthread_getname_np(pthread_self(), name, sizeof(name)); while (true) { LockGuard _lock = LockGuard(&_mutex); // 内部加锁 // 锁区 { // 此处是while!!!防止伪等待 while (_taskq.empty() && _isrunning) { // 线程等待 // 待被入队列的信号唤醒 _cond.Wait(&_mutex); } // 处理完了&&具备退出背景 if (_taskq.empty() && !_isrunning) { LOG(LogLevel::INFO) << name << "线程成功退出"; break; } T t = _taskq.front(); _taskq.pop(); } t(); // 任务执行(非临界区资源) } }

难点:
1.想到新增_isrunning成员以调和线程存在情况。
2.即将退出时Cond::BroadCast()的时机。

std::queue<T> _taskq

目的:实现队列元素模板化自动构造。 // T承载函数(任务)类型。

原理:T t; 接受队列元素,t(); 执行函数(任务)

//模板类型的前置声明 template<typename T> void QuTemp(); int main() { QuTemp<void (*)()>(); return 0; } void Print() { printf("test\n"); } template<typename T> void QuTemp() { //T队列维护函数。使用T对象运行 std::queue<T> _taskq; _taskq.push(Print); T t = _taskq.front(); _taskq.pop(); t(); }
void Stop()
// 线程池退出 void Stop() { if (!_isrunning) return; _isrunning = false; // handlerTask()需要改动目标是实现即使Stop()依然确保线程 任务完成后退出 // && 休眠线程呢? 为什么休眠线程要唤醒? 主线程直接退出不可以吗? // 对于当前设计的线程池目标是完全可以主线程直接退出,等待线程不予理睬。 // 但出于代码健壮性与可重复利用的价值还是处理为好。 // _cond.BroadCast(); WakeAThrd();//类内封装 }

宏的兼容bug

类型:作用域兼容

定义在命名空间内的宏虽然在命名空间内但是仍然属于全局,即若.hpp内定义LOG(level)宏在其他文件内#include"Log.hpp"文件即可使用该宏。

但是报错位置很迷,是LOG的位置。实际上原因是"LogLevel"的强枚举类型(enum class LogLevel)宏定义#中拥有使用了logger这一类名而导致的找不到,同样的LogLeval也找不到。

反思:

1.定位错误,定位不要仅根据报错位置。
2.画图明确框架包含关系制定解决方案。

感谢支持,长期连载
欢迎关注

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/17 6:18:59

YUM包管理器核心命令与配置实战指南

1. YUM包管理器概述yum&#xff08;Yellowdog Updater Modified&#xff09;是Red Hat系列Linux发行版中经典的RPM包管理工具&#xff0c;它通过自动解决软件依赖关系&#xff0c;极大简化了系统管理员和开发者的软件管理流程。作为RPM的前端工具&#xff0c;yum能够从配置的软…

作者头像 李华
网站建设 2026/7/17 6:18:49

Cocos2d-x中文乱码问题解决方案与优化

1. Cocos2d-x中文显示问题的本质原因在Cocos2d-x游戏开发中&#xff0c;中文显示乱码是一个常见但令人头疼的问题。这个问题的根源通常来自以下几个方面&#xff1a;1.1 编码格式不匹配Cocos2d-x默认使用的是UTF-8编码格式&#xff0c;而开发者可能使用了其他编码格式&#xff…

作者头像 李华
网站建设 2026/7/17 6:17:33

C++内存管理进阶:从new/delete到智能指针的实战指南

1. 项目概述&#xff1a;为什么C/C程序员必须精通内存管理&#xff1f;在C和C的世界里&#xff0c;内存管理是区分“会写代码”和“能写好代码”的一道分水岭。我见过太多项目&#xff0c;初期功能跑得飞快&#xff0c;运行几个月后却变得异常缓慢甚至崩溃&#xff0c;追根溯源…

作者头像 李华
网站建设 2026/7/17 6:15:16

MySQL级联复制中server_id冲突的排查与解决

1. 问题背景与现象描述最近在维护一个MySQL级联复制环境时&#xff0c;遇到了一个典型的同步异常问题。环境拓扑是这样的&#xff1a;旧集群主库&#xff08;Server A&#xff09;→ 新集群主库&#xff08;Server B&#xff09;→ 新集群从库&#xff08;Server C&#xff09;…

作者头像 李华
网站建设 2026/7/17 6:08:47

5分钟快速汉化Android Studio:新手必看的中文界面完整指南

5分钟快速汉化Android Studio&#xff1a;新手必看的中文界面完整指南 【免费下载链接】AndroidStudioChineseLanguagePack AndroidStudio中文插件(官方修改版本&#xff09; 项目地址: https://gitcode.com/gh_mirrors/an/AndroidStudioChineseLanguagePack 还在为Andr…

作者头像 李华
网站建设 2026/7/17 6:07:54

量化交易中的工程思维:从技术幻觉到系统化风控

上周&#xff0c;一位读者给我发来一段交易记录截图&#xff0c;附言说&#xff1a;“你看这个标题&#xff0c;像不像我们刚入市时的样子&#xff1f;”截图里赫然写着“3w重启全仓打满&#xff0c;第0天&#xff0c;绝不马后炮&#xff0c;一天干准一只&#xff0c;市场定规则…

作者头像 李华