继前俩次完成了日志系统的等级类、消息结构以及格式化消息,并且将格式化的数据实现落地。落地存在同步和异步的落地方式。
同步:本线程生成消息,并且进行IO写。异步:线程生成消息,交给子线程写。
为此实现了双缓冲区用来减少异步带来的频繁申请锁释放锁减低效率。
本文继续实现异步模块,并且完善日志器的功能。派生出异步日志器,并且通过单例模式创建全局的日志管理类。由建造者者模式创建全局日志器。
异步工作器
异步工作器的设计:
生产者调用异步工作器,将数据push到写缓冲区中(如果不可写就阻塞),并且唤醒工作线程。工作线程会立马将读写缓冲区交换,(得到数据)然后唤醒生产者可写数据。
工作线程拿到写缓冲区的内容后,就会立马实现落地。进行IO写。
所以异步工作器应该有的成员:
- 互斥锁
- 生产者条件变量
- 消费者条件变量
- 读缓冲区
- 写缓冲区
- 线程
- stop(控制工作器停止)
异步缓冲区对外提供的接口
- push(data,len)
- 线程入口(工作线程该做什么)
- stop(停止并且完成清理工作)
涉及到多线程模块,就会比较复杂,这里详细介绍一下
考虑到写缓冲区满了的做法 1.阻塞 2.扩容
阻塞属于安全的做法,等到读端将数据取出后才进行放数据
扩容属于不安全的做法,无限制的扩容会导致性能下降(极限测试才使用)
本博客的写法会针对安全和非安全俩种模式
push数据
1.先加锁 。
2.如果是扩容模块,就不需要等待读缓冲区拿数据,直接写即可 。如果安全模式就阻塞等待数据被取走。
3.往写缓冲区中放数据。
4.通知读缓冲区来取数据。
ThreadEntry()
- 循环事件
- 如果写缓冲区为空并且停止标志被设置,就退出
- 阻塞等待写缓冲区不为空
- 交换读写缓冲区
- 实现实际的数据落地
异步工作器的详细过程
/* 异步工作器
一种是安全的工作,缓冲区数据满了就等待,一种是不安全:无需等待,缓冲区无限扩容(适用极限性能下)
对外提供的接口:push数据 1.互斥锁2.是否阻塞 3.放数据 4.唤醒线程
threadEntry:线程入口 循环处理 1.互斥锁,读缓冲区是否空,是的话就交换 2.唤醒消费者 3.消费数据 4重置生产缓冲区
stop()
*/
namespace ns_logger
{
using Fun = std::function<void(Buffer &)>;
enum class AsyncSafeType
{
Async_Safe = 0,
Async_Unsafe
};
class AsyncLoop
{
public:
AsyncLoop(const Fun &func, AsyncSafeType anynctype = AsyncSafeType::Async_Safe)
: _safetype(anynctype), _stop(false), _func(func), _thread(std::thread(&AsyncLoop::ThreadEntry, this))
{
}
void Stop()
{
_stop = true;
// 唤醒所有的工作线程
_cons_cond.notify_all();
_thread.join();
}
~AsyncLoop()
{
Stop();
}
void Push(const char *data, size_t len)
{
// 往缓冲区内放数据
// 1.先判断是否为安全放,否则睡眠等待
// 2.放数据
// 唤醒消费线程
std::unique_lock<std::mutex> lock(_mutex);
if (_safetype == AsyncSafeType::Async_Safe)
_prod_cond.wait(lock, [&]()
{ return _prod_buffer.Writeable() >= len; });
// std::cout<<"异步器push:"<<data<<"_len:"<<len<<std::endl;
_prod_buffer.Push(data, len);
_cons_cond.notify_all();
}
void ThreadEntry()
{
while (1)
{
{
std::unique_lock<std::mutex> lock(_mutex);
// 只有当缓冲区的数据读取完毕&&是stop才break
if (_stop && _prod_buffer.Empty())
break;
//if (_safetype == AsyncSafeType::Async_Safe) // 安全状态下才需要唤醒
_cons_cond.wait(lock, [&]()
{ return _stop || !_prod_buffer.Empty(); });
_cons_buffer.Swap(_prod_buffer);
_prod_cond.notify_all();
}
// 回调任务处理
_func(_cons_buffer);
// 重置缓冲区
_cons_buffer.ReSet();
}
}
private:
Fun _func; // 线程处理缓冲区的任务
private:
AsyncSafeType _safetype;
std::atomic<bool> _stop;
std::mutex _mutex;
Buffer _prod_buffer;
Buffer _cons_buffer;
std::condition_variable _prod_cond;
std::condition_variable _cons_cond;
std::thread _thread; // 工作线程
};
using AsyncLoopPtr = std::shared_ptr<AsyncLoop>;
};
完善异步日志器
之前抽象日志器的基类,并且派生出同步日志器。现在完成了异步工作器的功能之后,就可以派生出异步日志器了。
异步日志器,重写log函数。调用异步工作器的log函数。
单例模式下的全局日志器管理类
对于一个日志器,能让其生命周期伴随整个项目。一个项目中也可能同时存在大量的全局日志器,它们可能来自不同的小组成员。所以对这些日志器进行管理。管理是以日志器的名称,建立散列表。同时日志器的管理类会创建一个默认日志器,用于快速的进行数据写日志。
默认日志器
格式是:日志等级、时间、日志器名称(root)、线程ID、文件名、行号、用户消息
默认日志的输出等级是debug
落地的方式是同步、标准输出落地
全局日志类管理类采用懒汉模式,只有在第一次调用的时候,才会创建单例。
管理类应该包含:
- 默认日志器:root
- map<name,日志器的智能指针>:用来管理日志器
- 互斥锁
对外提供的成员:
- getInstance:获取单例
- AddLogger:添加日志器
- getLogger:获取日志器
- getRootLogger:获取默认日志器
- IsExists:判断日志器是否存在
懒汉单例的新写法
private:
LoggerManager()
{
// 创建root
std::unique_ptr<LogcalLoggerBuilde> localbuild(new LogcalLoggerBuilde());
localbuild->BuildName("root");
_root_logger = localbuild->build();
AddLogger("root", _root_logger);
}
~LoggerManager() {}
LoggerManager &operator=(const LoggerManager &) = delete;
LoggerManager(const LoggerManager &) = delete;
public:
// 单例模式
static LoggerManager &GetInstance()
{
static LoggerManager _eton;
// 返回单例
// std::cout << "创建单例" << std::endl;
return _eton;
}
常规的增删查
建造者创建全局日志器
完成了全局日志器的管理后,再去利用建造者模式创建全局日志器。是一件非常轻松的事情,创建的步骤和局部日志器是一样的,无非就是在return 日志器之前,将日志器添加到单例中。
由于智能指针引用计数的特性,其生命周期被延长了。
// 构建全局的建造者
// 多了一步,把日志器添加进单例
class GlobalLoggerBuilder : public LoggerBuilde
{
public:
LoggerPtr build() override
{
assert(!_log_name.empty());
if (!_formatter.get())
_formatter = std::make_shared<Formatter>();
if (_sinks.empty())
_sinks.push_back(SinkFoctory::CreateSink<StdoutSink>());
LoggerPtr lp;
if (_type == LogType::ASYNC)
lp = std::make_shared<AsyncLogger>(_log_name, _limit_level, _formatter, _sinks, _safe_type);
else
lp = std::make_shared<SyncLogger>(_log_name, _limit_level, _formatter, _sinks);
LoggerManager::GetInstance().AddLogger(_log_name, lp);
// std::cout << "全局日志器构建完成" << std::endl;
return lp;
}
};
全局函数和宏替换简化操作(代理模式)
在实现日志器后,要对一个消息的打印,就必须先创建建造者。创建完建造者之后,提供build函数构建属性(格式化器、默认等级、同步异步、落地方式等)。最后build日志器,然后通过单例获取日志器,如果想要写日志,还需要手动输入文件名和行号,是比较麻烦的。这里就通过单例模式进行简化操作。
1.创建一个全局日志器
2.通过单例获取全局日志器
LoggerPtr lg = LoggerManager::GetInstance().GetLogger("ansync_logger");
3.手动传入文件名和行号
lg->Fatal(__FILE__, __LINE__, "测试全局日志器日志%d", count);
利用全局函数代理单例模式的获取
LoggerPtr getLogger(const std::string &name){
return LoggerManager::GetInstance().GetLogger(name);}
LoggerPtr getRootLogger(){
return LoggerManager::GetInstance().GetRootLogger(); }
利用宏替换,简化函数参数
//2.利用宏进行接口的简化
#define Debug(fmt,...) Debug(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define Info(fmt,...) Info(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define Warning(fmt,...) Warning(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define Error(fmt,...) Error(__FILE__,__LINE__,fmt,##__VA_ARGS__)
#define Fatal(fmt,...) Fatal(__FILE__,__LINE__,fmt,##__VA_ARGS__)
宏替换:省却创建默认日志器
//3.对默认日志器接口替换,不用再调用
#define DLOG(fmt,...) ns_logger::getRootLogger()->Debug(fmt,##__VA_ARGS__)
#define ILOG(fmt,...) ns_logger::getRootLogger()->Info(fmt,##__VA_ARGS__)
#define WLOG(fmt,...) ns_logger::getRootLogger()->Warning(fmt,##__VA_ARGS__)
#define ELOG(fmt,...) ns_logger::getRootLogger()->Error(fmt,##__VA_ARGS__)
#define FLOG(fmt,...) ns_logger::getRootLogger()->Fatal(fmt,##__VA_ARGS__)
代理模式之后的调用写日志
1.创建全局日志器
2.获取日志器
LoggerPtr lg = getLogger("ansync_logger");
3.写日志
lg->Debug("测试全局日志器日志%d", count++);
lg->Info("测试全局日志器日志%d", count++);
lg->Error("测试全局日志器日志%d", count++);
lg->Warning("测试全局日志器日志%d", count++);
lg->Fatal("测试全局日志器日志%d", count++);
while (count <= 20)
{
ELOG("测试全局日志器日志%d", count);
count++;
}
总体上,异步日志系统这个项目已经差不多要结束了。后面会进行压力测试,测试性能。最后谈谈这个项目的认识。
标签:__,异步,fmt,std,&&,缓冲区,日志,设计模式 From: https://blog.csdn.net/m0_73299809/article/details/141286627