同步/异步日志库
不定参函数
不定参宏函数
C⻛格不定参函数
C++⻛格不定参函数
设计模式
它不是语法规定,⽽是⼀ 套⽤来提⾼代码可复⽤性、可维护性、可读性、稳健性以及安全性的解决⽅案。
⽤抽象构建框架,⽤实现扩展细节
单⼀职责原则(SingleResponsibilityPrinciple)
类的职责应该单⼀,⼀个⽅法只做⼀件事。职责划分清晰了,每次改动到最⼩单位的⽅法或 类。
使⽤建议:两个完全不⼀样的功能不应该放⼀个类中,⼀个类中应该是⼀组相关性很⾼的函 数、数据的封装
⽤例:⽹络聊天:⽹络通信&聊天,应该分割成为⽹络通信类&聊天类
开闭原则是总纲,告诉我们要对扩展开放,对修改关闭。
单⼀职责原则告诉我们实现类要职责单⼀;
⾥⽒替换原则告诉我们不要破坏继承体系;
迪⽶特法则告诉我们要降低耦合;
接⼝隔离原则告诉我们在设计接⼝的时候要精简单⼀;
依赖倒置原则告诉我们要⾯向接⼝编程;
单例模式
C++11起,静态变量将能够在满⾜thread-safe的前提 下唯⼀地被构造和析构
// 懒汉模式template <typename T>
class Singleton {
private:Singleton(){}~Singleton(){}public:
Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static T& getInstance()
{
static Singleton _eton;return _eton;
}
};
⼯⼚模式
它提供了⼀种创建对象的最佳⽅式,在⼯⼚模式中,我们创建对象 时不会对上层暴露创建逻辑,⽽是通过使⽤⼀个共同结构来指向新创建的对象,以此实现创建-使⽤的分离。
//简单⼯⼚模式:通过参数控制可以⽣产任何产品// 优点:简单粗暴,直观易懂。使⽤⼀个⼯⼚⽣产同⼀等级结构下的任意产品// 缺点://1. 所有东西⽣产在⼀起,产品太多会导致代码量庞⼤
// 2.开闭原则遵循(开放拓展,关闭修改)的不是太好,要新增产品就必须修改⼯⼚⽅法。 #include <iostream>
#include <string>
#include <memory>class Fruit {public:Fruit(){}virtual void show() = 0;};class Apple : public Fruit {public:Apple() {}virtual void show() {std::cout << "我是⼀个苹果" << std::endl;}};class Banana : public Fruit {public:Banana() {}virtual void show() {std::cout << "我是⼀个⾹蕉" << std::endl;}};class FruitFactory {public:static std::shared_ptr<Fruit> create(const std::string &name) {if (name == "苹果") {return std::make_shared<Apple>();}else if(name == "⾹蕉") {return std::make_shared<Banana>();}return std::shared_ptr<Fruit>();}};int main(){std::shared_ptr<Fruit> fruit = FruitFactory::create("苹果");fruit->show();fruit = FruitFactory::create("⾹蕉");fruit->show();return 0;}
日志工具类实现:
// 工具类实现:
// 1.获取系统时间
// 2.获取文件大小
// 3.创建目录
// 4.获取文件所在目录#ifndef M_UTIL_H__
#define M_UTIL_H__#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <ctime>
#include <cassert>
#include <sys/stat.h>
#include <string.h>namespace tclog{namespace util{class Date{public:static size_t now(){ return (size_t)time(nullptr);}};class File{public:static bool exist(const std::string &name){if (name.empty()) return false;struct stat st;int result=stat(name.c_str(),&st);if (result == 0) {return true; // 文件存在} else if (errno == ENOENT) {return false; // 文件不存在} else {// 其他错误(权限不足等)std::cerr << "检查文件存在时出错: " << strerror(errno) << std::endl;return false;}}static std::string path(const std::string &name){if(name.empty()) return ".";size_t pos=name.find_last_of("/\\");if(pos==std::string::npos) return ".";return name.substr(0,pos+1);}static void create_directory(const std::string &path){if(path.empty()) return;if(exist(path)) return;size_t pos,idx=0;while(idx<path.size()){pos=path.find_first_of("/\\",idx);if(pos==std::string::npos){mkdir(path.c_str(),0755);return;}if(pos==idx){idx=pos+1;continue;}std::string subdir=path.substr(0,pos);if(subdir=="."||subdir==".."){idx=pos+1;continue;}if(exist(subdir)){idx=pos+1;continue;}mkdir(subdir.c_str(),0755);idx=pos+1;}}};}
}#endif
工具类模块单元测试:
#include"utils.hpp"int main(){std::cout<<tclog::util::Date::now()<<std::endl;std::string pathname="./test1/a/b.txt";tclog::util::File::create_directory(tclog::util::File::path(pathname));return 0;
}
日志等级类设计:
1.定义出日志系统所包含的所有日志等级
DRBUG 进⾏debug时候打印⽇志的等级
INFO 打印⼀些⽤⼾提⽰信息
WARN 打印警告信息
ERROR 打印错误信息
FATAL 打印致命信息-导致程序崩溃的信息
OFF 关闭所有⽇志输出
一般对于一个项目来说,都会设置一个默认的日志输出等级,只有当输出的日志等级大于默认限制等级才会进行输出。
2.提供一个接口,将枚举转换成对应的字符串
#ifndef M_LEVEL_H__
#define M_LEVEL_H__namespace tclog {class LogLevel{public:enum class value{UNKNOWN=0,DEBUG,INFO,WARN,ERROR,FATAL,OFF};static const *toString(LogLevel::value va){switch(va){case LogLevel::value::DEBUG: reurn "DEBUG";case LogLevel::value::INFO: reurn "INFO";case LogLevel::value::WARN: reurn "WARN";case LogLevel::value::ERROR: reurn "ERROR";case LogLevel::value::FATAL: reurn "FATAL";case LogLevel::value::OFF: reurn "OFF";}return "UNKNOWN";}}
}#endif
日志消息类设计
意义: 一条日志信息的所需的各类要素
1.日志输出时间 用于过滤日志输出时间
2.日志等级 用于进行日志过滤分析3.源文件名称
4.源代码行号5.线程ID
6.具体的日志信息
7.日志器名称 当前支持多日志器同时使用
#ifndef M_MSG_H__
#define M_MSG_H__
#include "util.hpp"
#include "level.hpp"
#include <thread>
#include <memory>namespace tclog{struct LogMsg{size_t _ctime; //时间LogLevel::value _level; //⽇志等级size_t _line; //⾏号std::thread::id _tid; //线程IDstd::string _logger; //日志器名称std::string _file; //源码文件名std::string _payload;//有效消息LogMsg(std::string &logger, std::string file, size_t line,std::string &&payload, LogLevel::value level): _logger(logger), _file(file),_payload(std::move(payload)),_level(level),_line(line), _ctime(util::date::now()),_tid(std::this_thread::get_id()) {}};}
#endif
日志输出格式化类设计
对日志消息进行格式化,组织成为指定格式的字符串
%d ⽇期
%T 缩进
%t 线程id
%p ⽇志级别
%c ⽇志器名称
%f ⽂件名
%l ⾏号
%m ⽇志消息
%n 换⾏
Linux O(1) 调度器(2.6内核之前)通过以下设计实现高效调度:
两个优先级数组(Priority Arrays):
活跃数组(Active Array):存放当前需要调度的任务。
过期数组(Expired Array):存放时间片已用完的任务。
调度器从活跃数组中选择最高优先级任务执行。
当活跃数组中所有任务的时间片都用完时,交换两个数组:
活跃数组变成过期数组(等待重新分配时间片)。
过期数组变成活跃数组(现在可以调度)。
交换后,调度器继续从新的活跃数组中选择任务。
这样,调度器在每次选择任务时都是O(1)复杂度(因为总是取活跃数组的最高优先级队列的头节点),而交换数组的操作是常数时间。
与双缓冲区设计的相似点
对比维度 | Linux O(1) 调度器 | 双缓冲区任务池(我的设计) |
---|---|---|
数据结构 | 两个数组:活跃数组 vs 过期数组 | 两个池:push池 vs pop池 |
读写分离 | 调度器读活跃数组,任务时间片用完写入过期数组 | 生产者写push池,消费者读pop池 |
交换时机 | 当活跃数组所有任务时间片耗尽 | 当pop池任务全部处理完毕 |
交换操作 | 交换两个数组的指针(常数时间) | 交换两个池子的指针(常数时间) |
同步需求 | 交换时需要短暂锁(防止竞争) | 交换时需要一次互斥(避免生产者消费者冲突) |
优点 | 避免动态优先级计算,调度O(1) | 减少锁竞争,生产消费几乎并行 |
为什么说思路相似?
“交换”代替“逐个迁移”:
两者都避免逐个处理元素(例如不逐个移动任务或任务项),而是通过交换整个容器来重置状态。
状态分离:
O(1)调度器将“待调度”和“已过期”任务分离到两个数组。
双缓冲区将“待处理”和“正在处理”的任务分离到两个池子。
高效同步:
两者都只在交换时需要同步(锁),其他时间读写操作可以并行。
区别
目的不同:
O(1)调度器解决的是任务调度效率问题(避免遍历整个任务链表)。
双缓冲区解决的是生产者-消费者锁竞争问题(减少互斥开销)。
交换触发条件:
O(1)调度器:活跃数组任务时间片全部耗尽。
双缓冲区:pop池任务全部处理完毕。
总结
这种双缓冲区设计确实与 Linux O(1)调度算法 的处理思路非常相似,都采用了:
双数据结构(数组/池子)分离状态。
交换指针(而非逐个迁移元素)来重置状态。
仅在交换时同步(锁),最大化并行度。