首页 > 其他分享 >【项目】多设计模式下的同步&&异步日志系统(三)

【项目】多设计模式下的同步&&异步日志系统(三)

时间:2024-08-18 17:51:37浏览次数:11  
标签:__ 异步 fmt std && 缓冲区 日志 设计模式

继前俩次完成了日志系统的等级类、消息结构以及格式化消息,并且将格式化的数据实现落地。落地存在同步和异步的落地方式。

同步:本线程生成消息,并且进行IO写。异步:线程生成消息,交给子线程写。

为此实现了双缓冲区用来减少异步带来的频繁申请锁释放锁减低效率。

本文继续实现异步模块,并且完善日志器的功能。派生出异步日志器,并且通过单例模式创建全局的日志管理类。由建造者者模式创建全局日志器。

异步工作器

异步工作器的设计:

生产者调用异步工作器,将数据push到写缓冲区中(如果不可写就阻塞),并且唤醒工作线程。工作线程会立马将读写缓冲区交换,(得到数据)然后唤醒生产者可写数据。

工作线程拿到写缓冲区的内容后,就会立马实现落地。进行IO写。

所以异步工作器应该有的成员:

  • 互斥锁
  • 生产者条件变量
  • 消费者条件变量
  • 读缓冲区
  • 写缓冲区
  • 线程
  • stop(控制工作器停止)

异步缓冲区对外提供的接口

  1. push(data,len)
  2. 线程入口(工作线程该做什么)
  3. 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,日志器的智能指针>:用来管理日志器
  • 互斥锁

 对外提供的成员:

  1. getInstance:获取单例
  2. AddLogger:添加日志器
  3. getLogger:获取日志器
  4. getRootLogger:获取默认日志器
  5. 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

相关文章

  • 026、Vue3+TypeScript基础,使用async和await来异步读取axios的网络图片
    01、App.vue代码如下:<template><divclass="app"><h2>App.Vue</h2><Person/></div></template><scriptlang="ts"setupname="App">//JS或TSimportPersonfrom'./......
  • 设计模式六大原则之:开闭原则
    1.开闭原则简介开闭原则(OpenClosedPrinciple,OCP)‌是面向对象程序设计(OOP)中的一个基本原则,也是软件工程中的一项重要原则。它的核心思想是:一个软件实体(如类、模块或函数)应该对扩展开放,即当需求变化时,可以通过添加新的代码进行扩展来满足新的需求,而不需要修改现有的代码。......
  • 高级java每日一道面试题-2024年8月16日-设计模式篇-解释装饰者模式和代理模式的区别?
    如果有遗漏,评论区告诉我进行补充面试官:解释装饰者模式和代理模式的区别?我回答:在Java中,装饰者模式(DecoratorPattern)和代理模式(ProxyPattern)都是常用的设计模式,它们在结构上看起来有些相似,但实际上它们的目的、应用场景和实现方式存在明显的区别。下面详细解释这两种......
  • LabVIEW异步同步模式
    LabVIEW 的异步和同步模式在数据流编程和任务执行方面有不同的应用场景。以下是对这两种模式的详细介绍和比较。1. 同步模式同步模式指的是任务按照一定的顺序依次执行,前一个任务必须完成后,后一个任务才能开始。具体来说,在 LabVIEW 中,如果一个 VI(虚拟仪器)调用另一个 ......
  • 直播系统源码,异步加载UI相关知识点
    简述FutureBuilder和StreamBuilder都是Flutter为开发者提供快速实现异步加载UI的方案,它们不同的是一个依赖的是Future另一个依赖的是Stream,一般情况下我们使用Future就可以完成大部分的异步操作,它可以异步加载我们需要的数据,那Stream是不是多余的呢?并不是,Stream专门用于异步加......
  • Golang使用Option设计模式优雅处理可选参数
    go语言不像其他语言函数的参数可以设置默认值以下是参考第三方库的写法packagemainimport"fmt"typeUserstruct{namestringageintidint}//Option代表可选参数typeOptionfunc(foo*User)//WithName为name字段提供一个设置器funcWithName(name......
  • 设计模式---构建者模式(Builder Pattern)
    构建者模式(BuilderPattern)是一种创建型设计模式,旨在将复杂对象的构建过程与其表示分离。它允许使用相同的构建过程创建不同的表示。该模式通常用于构建复杂对象,这些对象由多个部分组成或具有多个可选属性。构建者模式的核心要素:Builder(构建者):定义构建对象的接口,声明创建部......
  • 【通信理论知识】数据传送的方式:串/并行;传输方向:单工、半/全双工;传输方式:同步/异步
    串行/并行通信按数据传送的方式,通讯可分为串行通讯与并行通讯。串行通讯就像单个车道的公路,同一时刻只能传输一个数据位的数据。并行通讯就像多个车道的公路,可以同时传输多个数据位的数据。传输方向(单工、半/全双工)全双工和半双工通信的本质区别(SPI、IIC)半双工......
  • 【Java Lambda系列】新玩法,用Lambda重构设计模式
    前言前面三章通过理论+案例的方式对Lambda的描述,应该能基本上解决大家日常开发中所遇到的Lambda问题,为了更好的展现Lambda魅力,和加深巩固Lambda知识点,今天咱们讨论Lambda如何重构设计模式!关于设计模式众所周知,设计模式是一群大佬程序员将对程序设计的经验归纳总结起来的......
  • 使用 onNuxtReady 进行异步初始化
    title:使用onNuxtReady进行异步初始化date:2024/8/16updated:2024/8/16author:cmdragonexcerpt:摘要:本文详细介绍了Nuxt.js框架中的onNuxtReady函数用途、使用场景及其实现步骤,并通过集成分析库的示例代码,指导开发者如何在应用初始化完成后执行异步操作,以优化用户体......