日志与线程池

@bit::Shadow
✧(≖ ◡ ≖✿

目录

日志

日志介绍

日志的中心实现

5个亮点

0.策略模式

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

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

3.重载<<递推返回

[4.enum class强枚举类型支持](#4.enum class强枚举类型支持)

开发顺序

线程池

线程封装

制作线程池

框架图

​编辑

[ThreadPool(int num)](#ThreadPool(int num))

[void HandlerTask()](#void HandlerTask())

[std::queue _taskq](#std::queue _taskq)

[void Stop()](#void Stop())

宏的兼容bug


日志

日志介绍

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

日志是什么?

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

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

日志的中心实现

仅实现日志中关键片段

cpp 复制代码
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)分隔符(兼容性极好缓冲区刷新行为)
cpp 复制代码
//const char* gsep = "\r\n";
const std::string gsep = "\r\n";
//Win下记事本显示兼容
//Linux 下 \r被忽略,只识别 \n,所以也兼容
//HTTP兼容

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

利用string的自动扩容管理。

支持接口类型很多,更加灵活。

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

注意点:1.宏定义++不要随意加注释++ 内容。 易造成宏混乱

2.lv与lv的++传递++ 兼容。 "宏函数"

3.预处理宏__FILE__与__LINE__

3.重载<<递推返回
cpp 复制代码
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强枚举类型支持
cpp 复制代码
enum class LogLevel
{
    DEBUG,
    INFO,
    WARN,
    ERROR,
    FATAL
};

开发顺序

第1步:核心基础设施

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

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

第4步:日志器(组装)

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

待🔗C++进阶知识4.0

线程池

线程封装

gitee

cpp 复制代码
#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(); 通过封装的线程构造参数,被同等地分配到各个线程。

cpp 复制代码
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 = new ThreadPool<T>()"内部调用构造函数" };单if条件下------若快进程先new,慢进程后判断nullptr。可能导致慢进程误判仍为空,所以需要双重if。

cpp 复制代码
// 区别线程分发任务接口
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(); 执行函数(任务)。

cpp 复制代码
//模板类型的前置声明
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()
cpp 复制代码
// 线程池退出
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.画图明确框架包含关系制定解决方案。

感谢支持,长期连载

欢迎关注

相关推荐
冷小鱼1 小时前
AI Agent 的核心算法:多智能体协作(Multi-Agent Systems)
前端·人工智能·算法·multi-agent·多智能体协作·systems
QN1幻化引擎1 小时前
# DalinX V8 灵鉴 V2:12维意识评测框架 —— 从 Tononi IIT 到 Friston FEP 的理论统一
数据库·人工智能·算法·机器学习·数据挖掘·agi
牧以南歌〆1 小时前
数据结构<一>顺序表
c语言·数据结构·算法
樂禮1 小时前
Windows-Arch Linux 键鼠共享 & 剪贴板同步配置
linux·运维·windows
CHANG_THE_WORLD1 小时前
Linux C 多线程 TCP 并发服务器:从 `accept()` 到工作线程的完整解析
linux·服务器·c语言
影视飓风TIM1 小时前
Linux基础入门笔记:命令、内核架构与压缩传输全梳理
linux·笔记·架构
我不是懒洋洋1 小时前
从零实现一个分布式限流器:令牌桶与漏桶
c++
敲上瘾1 小时前
Linux系统调用mmap文件映射与malloc简单模拟实现指南
linux·服务器·c语言·缓存
Fairy要carry2 小时前
面试-训练显存占用估计
算法