C++项目实战——基于多设计模式下的同步异步日志系统-⑩-异步缓冲区类与异步工作器类设计

本文主要是介绍C++项目实战——基于多设计模式下的同步异步日志系统-⑩-异步缓冲区类与异步工作器类设计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 专栏导读
  • 异步缓冲区设计思想
  • 异步缓冲区类设计
  • 异步工作器类设计
  • 异步日志器设计
  • 异步缓冲区类整理
  • 异步工作器类整理

专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 C++项目——基于多设计模式下的同步与异步日志系统

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法Linux

在这里插入图片描述
为了避免因为写日志的过程阻塞,导致业务线程在写日志的时候影响其效率(例如由于网络原因导致日志写入阻塞,进而导致业务线程阻塞),因此我们需要设计一个异步日志器

异步的思想就是不让业务线程进行日志的实际落地操作,而是将日志消息放到缓冲区(一块指定内存)当中,接下来有一个专门的异步线程,去针对缓冲区中的数据进行处理(实际落地操作)

所以,异步日志器的实现思想:

  • 设计一个线程安全的缓冲区
  • 创建一个异步工作线程,专门负责缓冲区中日志消息落地操作。

异步缓冲区设计思想

在任务池的设计中,有很多备选方案,比如队列、循环队列等,但是不管哪一种都会涉及到锁冲突的情况,因为在生产者与消费者模型中,任何两个角色之间具有互斥关系,因此每一次任务的添加与取出都有可能涉及锁的冲突

所以我们采用双缓冲区的的设计思想,优势在于:

  • 避免了空间的频繁申请与释放,且尽可能的减少了生产者与消费则之间锁冲突的概率,提高了任务处理效率

双缓冲区的设计思想是:采用两个缓冲区,一个用来进行任务写入(push pool),一个进行任务处理(pop pool)。当异步工作线程(消费者)将缓冲区中的数据全部处理完毕之后,然后交换两个缓冲区,重新对新的缓冲区中的任务进行处理,虽然同时多线程写入也会产生冲突,但是冲突并不会像每次只处理一条的时候频繁(减少了消费者与生产者之间的锁冲突),且不涉及到空间的频繁申请释放所带来的的消耗。

在这里插入图片描述

异步缓冲区类设计

类中包含的成员:

  • 一个存放字符串数据的缓冲区(使用vector进行空间管理);
  • 当前写入数据位置的指针(指向可写区域的起始位置,避免数据的写入覆盖);
  • 当前读取数据位置的指针(指向可读区域的起始位置,当读取指针与写入指针指向相同的位置表示数据读取完了);

类中提供的操作:

  • 向缓冲区中写入数据
  • 获取可读数据起始地址的接口
  • 获取可读数据长度的接口
  • 移动读写位置的接口
  • 初始化缓冲区的操作(将读写位置初始化–在一个缓冲区所有数据处理完毕之后);
  • 提供交换缓冲区的操作(交换空间地址,并不交换空间数据)。

注意,缓冲区中直接存放格式化后的日志消息字符串,而不是LogMsg对象,这样做有两个好处:

  • 减少了LogMsg对象频繁的构造的消耗;
  • 可以针对缓冲区中的日志消息,一次性进行IO操作,减少IO次数,提高效率。
#ifndef __M_BUFFER_H__
#define __M_BUFFER_H__
#include "util.hpp"
#include <vector>
#include <cassert>namespace LOG
{#define DEFAULT_BUFFER_SIZE (1 * 1024 * 1024)#define THRESHOLD_BUFFER_SIZE (8 * 1024 * 1024)#define INCREMENT_BUFFER_SIZE (1 * 1024 * 1024)class Buffer {public:Buffer() : _buffer(DEFAULT_BUFFER_SIZE), _writer_idx(0), _reader_idx(0) {}// 向缓冲区中写入数据void push(const char* data, size_t len){// 1.考虑空间不够则扩容ensureEnoughSize(len);// 2.将数据拷贝到缓冲区std::copy(data, data + len, &_buffer[_writer_idx]);// 3.将当前写入位置向后偏移moveWriter(len);}// 返回可读数据的起始地址const char* begin(){return &_buffer[_reader_idx];}// 返回可写数据的长度size_t writeAbleSize(){// 对于扩容思路并没有用, 仅针对固定大小缓冲区return (_buffer.size() - _writer_idx);}// 返回可读数据的长度size_t readAbleSize(){ return (_writer_idx - _reader_idx);}// 重置读写位置void reset(){_writer_idx = 0;_reader_idx = 0;}// 对buffer实现交换操作void swap(Buffer & buffer){_buffer.swap(buffer._buffer);std::swap(_reader_idx, buffer._reader_idx);std::swap(_writer_idx, buffer._writer_idx);}// 判断缓冲区是否为空bool empty(){return (_reader_idx == _writer_idx);}private:void ensureEnoughSize(size_t len){if(len < writeAbleSize()) return;size_t new_size = 0;if(_buffer.size() < THRESHOLD_BUFFER_SIZE){new_size = _buffer.size() * 2 + len; // 小于阈值则翻倍增长}else{new_size = _buffer.size() + INCREMENT_BUFFER_SIZE + len;}_buffer.resize(new_size);}// 对读指针进行向后偏移操作void moveReader(size_t len){assert(len <= readAbleSize());_reader_idx += len;}// 对写指针进行向后偏移操作void moveWriter(size_t len){assert(len + _writer_idx<= _buffer.size());_writer_idx += len;}private:std::vector<char> _buffer;size_t _reader_idx; // 当前可读数据的指针size_t _writer_idx; // 当前可写数据的指针};
}
#endif

异步工作器类设计

异步工作器的主要任务是,对缓冲区中的数据进行处理,若处理缓冲区中没有数据了则交换缓冲区

异步工作器类管理的成员有:

  • 双缓冲区(生产,消费);
  • 互斥锁:保证线程安全;
  • 条件变量-生产&消费:生产缓冲区中没有数据,处理完消费缓冲区数据后就休眠;
  • 回调函数:针对缓冲区中数据的处理接口——外界传入一个函数,告诉异步日志器该如何处理。

异步工作器类提供的操作有:

  • 停止异步工作器
  • 添加数据到缓冲区

私有操作:

  • 创建线程
  • 线程入口函数:在线程入口函数中交换缓冲区,对消费缓冲区数据使用回调函数进行处理,处理完后再次交换;
#ifndef __M_LOOPER_H__
#define __M_LOOPER_H__
#include "buffer.hpp"
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>namespace LOG
{using Functor = std::function<void(Buffer &)>;enum class AsyncType{ASYNC_SAFE, // 安全状态, 表示缓冲区满了则阻塞,避免资源耗尽的风险ASYNC_UNSAFE // 不考虑资源耗尽的问题};class AsyncLooper{public:using ptr = std::shared_ptr<AsyncLooper>;AsyncLooper(const Functor& cb, AsyncType looper_type = AsyncType::ASYNC_SAFE):_looper_type(looper_type),_stop(false),_call_back(cb),_thread(std::thread(&AsyncLooper::threadEntry, this)){}~AsyncLooper() { stop(); }void stop() {_stop = true; // 将退出标志设置为true_con_cond.notify_all(); // 唤醒所有工作线程_thread.join(); // 等待工作线程退出}void push(const char* data, size_t len){std::unique_lock<std::mutex> lock(_mutex);// 条件变量控制,若缓冲区剩余空间大小等于数据长度,则可以添加数据if(_looper_type == AsyncType::ASYNC_SAFE)_pro_cond.wait(lock, [&](){ return _pro_buf.writeAbleSize() >= len; });// 能够走下来说明条件满足,可以向缓冲区添加数据了_pro_buf.push(data, len);// 唤醒消费者对缓冲区中的数据进行处理_con_cond.notify_one();}private:// 线程入口函数 -- 对消费者缓冲区中的数据进行处理,处理完毕后,初始化缓冲区,交换缓冲区void threadEntry(){// 为互斥锁设置一个声明周期,当缓冲区交换完毕就解锁while(1){{// 1.判断生产缓冲区有没有数据,有则交换,无则阻塞std::unique_lock<std::mutex> lock(_mutex);if(_stop && _pro_buf.empty()) break;// 若当前是退出前被唤醒或者是有数据被唤醒,则返回真,继续向下运行,否则重新进入休眠_con_cond.wait(lock, [&](){ return _stop || !_pro_buf.empty(); });_con_buf.swap(_pro_buf);// 2.唤醒生产者if(_looper_type == AsyncType::ASYNC_SAFE)_pro_cond.notify_all();}// 3.被唤醒后,对消费者缓冲区进行数据处理_call_back(_con_buf);// 4.初始化消费者缓冲区_con_buf.reset();}}private:Functor _call_back;private:AsyncType _looper_type;std::atomic<bool> _stop;Buffer _pro_buf;Buffer _con_buf;std::mutex _mutex;std::condition_variable _pro_cond;std::condition_variable _con_cond;std::thread _thread; // 异步工作器对应的工作线程};
}
#endif

异步日志器设计

异步日志器继承自日志器类,并在同步日志器类上拓展了异步工作器。当我们需要异步输出日志的时候,需要创建异步日志器和消息处理器,调用异步日志器的log、debug、error、info、fatal等函数输出不同级别日志。

  • log函数为重写Logger类的函数,主要实现将日志日志数据加入异步缓冲区;
  • realLog函数主要由异步线程调用(是为异步工作器设置的回调函数),完成日志的实际落地操作。
class AsyncLogger : public Logger
{
public:AsyncLogger(const std::string &logger_name,LogLevel::value level,LOG::Formatter::ptr &formatter,std::vector<LogSink::ptr> &sinks,AsyncType looper_type): Logger(logger_name, level, formatter, sinks),_looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::realLog, this, std::placeholders::_1), looper_type)){}// 将数据写入缓冲区 void log(const char *data, size_t len){_looper->push(data, len);}// 设计一个实际落地函数void realLog(Buffer &buf){if (_sinks.empty())return;for (auto &sink : _sinks){sink->log(buf.begin(), buf.readAbleSize());}}private:AsyncLooper::ptr _looper; // 异步工作器
};

异步缓冲区类整理

#ifndef __M_BUFFER_H__
#define __M_BUFFER_H__
#include "util.hpp"
#include <vector>
#include <cassert>namespace LOG
{#define DEFAULT_BUFFER_SIZE (1 * 1024 * 1024)#define THRESHOLD_BUFFER_SIZE (8 * 1024 * 1024)#define INCREMENT_BUFFER_SIZE (1 * 1024 * 1024)class Buffer {public:Buffer() : _buffer(DEFAULT_BUFFER_SIZE), _writer_idx(0), _reader_idx(0) {}// 向缓冲区中写入数据void push(const char* data, size_t len){// 1.考虑空间不够则扩容ensureEnoughSize(len);// 2.将数据拷贝到缓冲区std::copy(data, data + len, &_buffer[_writer_idx]);// 3.将当前写入位置向后偏移moveWriter(len);}// 返回可读数据的起始地址const char* begin(){return &_buffer[_reader_idx];}// 返回可写数据的长度size_t writeAbleSize(){// 对于扩容思路并没有用, 仅针对固定大小缓冲区return (_buffer.size() - _writer_idx);}// 返回可读数据的长度size_t readAbleSize(){ return (_writer_idx - _reader_idx);}// 重置读写位置void reset(){_writer_idx = 0;_reader_idx = 0;}// 对buffer实现交换操作void swap(Buffer & buffer){_buffer.swap(buffer._buffer);std::swap(_reader_idx, buffer._reader_idx);std::swap(_writer_idx, buffer._writer_idx);}// 判断缓冲区是否为空bool empty(){return (_reader_idx == _writer_idx);}private:void ensureEnoughSize(size_t len){if(len < writeAbleSize()) return;size_t new_size = 0;if(_buffer.size() < THRESHOLD_BUFFER_SIZE){new_size = _buffer.size() * 2 + len; // 小于阈值则翻倍增长}else{new_size = _buffer.size() + INCREMENT_BUFFER_SIZE + len;}_buffer.resize(new_size);}// 对读指针进行向后偏移操作void moveReader(size_t len){assert(len <= readAbleSize());_reader_idx += len;}// 对写指针进行向后偏移操作void moveWriter(size_t len){assert(len + _writer_idx<= _buffer.size());_writer_idx += len;}private:std::vector<char> _buffer;size_t _reader_idx; // 当前可读数据的指针size_t _writer_idx; // 当前可写数据的指针};
}
#endif

异步工作器类整理

#ifndef __M_LOOPER_H__
#define __M_LOOPER_H__
#include "buffer.hpp"
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <functional>
#include <memory>namespace LOG
{using Functor = std::function<void(Buffer &)>;enum class AsyncType{ASYNC_SAFE, // 安全状态, 表示缓冲区满了则阻塞,避免资源耗尽的风险ASYNC_UNSAFE // 不考虑资源耗尽的问题};class AsyncLooper{public:using ptr = std::shared_ptr<AsyncLooper>;AsyncLooper(const Functor& cb, AsyncType looper_type = AsyncType::ASYNC_SAFE):_looper_type(looper_type),_stop(false),_call_back(cb),_thread(std::thread(&AsyncLooper::threadEntry, this)){}~AsyncLooper() { stop(); }void stop() {_stop = true; // 将退出标志设置为true_con_cond.notify_all(); // 唤醒所有工作线程_thread.join(); // 等待工作线程退出}void push(const char* data, size_t len){std::unique_lock<std::mutex> lock(_mutex);// 条件变量控制,若缓冲区剩余空间大小等于数据长度,则可以添加数据if(_looper_type == AsyncType::ASYNC_SAFE)_pro_cond.wait(lock, [&](){ return _pro_buf.writeAbleSize() >= len; });// 能够走下来说明条件满足,可以向缓冲区添加数据了_pro_buf.push(data, len);// 唤醒消费者对缓冲区中的数据进行处理_con_cond.notify_one();}private:// 线程入口函数 -- 对消费者缓冲区中的数据进行处理,处理完毕后,初始化缓冲区,交换缓冲区void threadEntry(){// 为互斥锁设置一个生命周期,当缓冲区交换完毕就解锁while(1){{// 1.判断生产缓冲区有没有数据,有则交换,无则阻塞std::unique_lock<std::mutex> lock(_mutex);if(_stop && _pro_buf.empty()) break;// 若当前是退出前被唤醒或者是有数据被唤醒,则返回真,继续向下运行,否则重新进入休眠_con_cond.wait(lock, [&](){ return _stop || !_pro_buf.empty(); });_con_buf.swap(_pro_buf);// 2.唤醒生产者if(_looper_type == AsyncType::ASYNC_SAFE)_pro_cond.notify_all();}// 3.被唤醒后,对消费者缓冲区进行数据处理_call_back(_con_buf);// 4.初始化消费者缓冲区_con_buf.reset();}}private:Functor _call_back;private:AsyncType _looper_type; // 选择异步工作器工作模式(安全与非安全模式)std::atomic<bool> _stop; // 工作器退出标志Buffer _pro_buf; Buffer _con_buf; std::mutex _mutex;std::condition_variable _pro_cond;std::condition_variable _con_cond;std::thread _thread; // 异步工作器对应的工作线程};
}
#endif

在这里插入图片描述

这篇关于C++项目实战——基于多设计模式下的同步异步日志系统-⑩-异步缓冲区类与异步工作器类设计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/224358

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置