当前位置: 首页 > news >正文

异步日志系统

AsynLogging类,核心功能是通过后台线程异步写入日志,避免前端线程因 IO 操作阻塞,提升系统性能。

一、核心设计思路

异步日志的核心是 “前端生产日志,后端消费日志”的分离模式:

  • 前端线程(业务线程)通过append方法将日志写入内存缓冲区,操作高效(无磁盘 IO)。
  • 后端线程(日志线程)定期或被唤醒后,将缓冲区中的日志批量写入磁盘文件。
2. 成员变量(注释中声明的核心变量)
变量名作用
flushInterval_日志刷新间隔(如 3 秒,后端线程定期刷新)
running_原子变量,标记日志系统是否运行(线程安全的状态控制)
basename_日志文件基础名称(用于生成滚动日志文件名)
rollSize_日志文件滚动阈值(超过该大小则创建新文件,复用LogFile的滚动逻辑)
pthread_后台日志线程的智能指针
mutex_ + cond_互斥锁 + 条件变量,用于前端与后端线程的同步
currentBuffer_前端当前使用的日志缓冲区(字符串形式)
buffers_已填满的缓冲区队列(等待后端线程处理)
output_LogFile对象,负责实际的日志文件写入和滚动管理
latch_倒计时门闩(用于确保后台线程启动完成后,前端才开始写入日志)

AsynLogging::append 方法中判断是否需要切换当前日志缓冲区的核心条件,用于决定是否将当前缓冲区(currentBuffer_)移入待处理队列并创建新缓冲区,具体解析如下:

条件逻辑

if (currentBuffer_.size() >= BufMaxLen ||currentBuffer_.capacity() - currentBuffer_.size() < len)
  • 两个判断条件用 ||(逻辑或)连接,满足任意一个即触发缓冲区切换。

条件 1:currentBuffer_.size() >= BufMaxLen

  • 含义:当前缓冲区的已使用大小(size())大于等于预设的最大容量(BufMaxLen = 4KB)。
  • 作用:确保单个缓冲区不会无限增长,当达到上限时,强制将其移入待处理队列(buffers_),由后端线程写入磁盘。

条件 2:currentBuffer_.capacity() - currentBuffer_.size() < len

  • 含义:当前缓冲区的剩余可用空间(总容量 capacity() 减去已使用大小 size())小于本次要写入的日志长度(len)。
  • 作用:避免因 “剩余空间不足” 导致日志被截断或缓冲区被迫扩容。即使缓冲区未填满(未达 BufMaxLen),但剩余空间不够写入当前日志时,也会提前切换缓冲区,保证日志完整性。

触发后的操作

当上述条件满足时,代码会执行:

buffers_.push_back(std::move(currentBuffer_));  // 将当前缓冲区移入待处理队列
currentBuffer_.reserve(BufMaxLen);              // 重置新缓冲区,预留最大容量
  • 通过 std::move 转移当前缓冲区的所有权到队列,避免拷贝开销。
  • 新缓冲区预分配 BufMaxLen 大小的空间,减少后续写入时的内存分配次数。

设计目的

  1. 保证日志完整性:避免因空间不足导致日志片段化或丢失。
  2. 控制内存占用:单个缓冲区大小不超过 4KB,防止内存过度消耗。
  3. 减少 IO 次数:缓冲区满或空间不足时才切换,实现批量写入,提升效率。
void AsynLogging::append(const char *msg, const size_t len)
{std::unique_lock<std::mutex> lock(mutex_);  // 加锁保证线程安全// 若当前缓冲区不足(已满或剩余空间不够),则将其移入队列,换新缓冲区if (currentBuffer_.size() >= BufMaxLen || currentBuffer_.capacity() - currentBuffer_.size() < len){buffers_.push_back(std::move(currentBuffer_));  // 转移当前缓冲区所有权currentBuffer_.reserve(BufMaxLen);              // 重置新缓冲区}currentBuffer_.append(msg, len);  // 写入日志到当前缓冲区cond_.notify_all();               // 唤醒后端线程处理缓冲区
}
  • 核心作用:前端线程写入日志到内存缓冲区,避免直接写磁盘。
  • 线程安全:通过mutex_加锁,支持多线程并发写入。
  • 缓冲区切换:当当前缓冲区不足时,将其移入待处理队列,创建新缓冲区继续写入。
  • 唤醒后端:写入后通知后端线程有数据待处理。

倒计时门闩(CountDownLatch) 类

用于多线程同步场景,核心功能是等待一个或多个线程完成特定操作后,再继续执行当前线程。

一、类的核心成员(注释中声明)

  • count_:倒计时计数器,初始化为指定值,每调用一次countDown()减 1,直至为 0。
  • mutex_:互斥锁,用于保护count_的线程安全访问。
  • cond_:条件变量,用于线程间的等待与唤醒机制。

二、核心方法解析

1. 构造函数 CountDownLatch(int count)
CountDownLatch::CountDownLatch(int count) : count_(count) {}
  • 初始化count_为传入的计数初始值(例如,若需要等待 3 个线程完成,则初始化为 3)。
2. wait() 方法
void CountDownLatch::wait()
{std::unique_lock<std::mutex> lock(mutex_);  // 加锁,确保线程安全while(count_ > 0)  // 循环判断,避免虚假唤醒{cond_.wait(lock);  // 释放锁并阻塞等待,被唤醒时重新获取锁}
}
  • 作用:调用该方法的线程会阻塞,直到count_减为 0 才继续执行。
  • 线程安全:通过std::unique_lock加锁,保证对count_的访问互斥。
  • 防止虚假唤醒:使用while循环而非if判断,确保被唤醒后再次检查count_是否真的为 0(条件变量可能因系统原因虚假唤醒)。
3. countDown() 方法
void CountDownLatch::countDown()
{std::unique_lock<std::mutex> lock(mutex_);  // 加锁count_ -= 1;  // 计数器减1if(count_ == 0)  // 当计数器归0时{cond_.notify_all();  // 唤醒所有等待的线程}
}
  • 作用:每调用一次,计数器count_减 1;当count_变为 0 时,唤醒所有通过wait()阻塞的线程。
  • 线程安全:加锁确保count_的修改是原子操作,避免多线程并发修改导致的计数错误。
4. getCount() 方法
int CountDownLatch::getCount() const
{std::unique_lock<std::mutex> lock(mutex_);  // 加锁return count_;  // 返回当前计数器值
}
  • 作用:获取当前count_的数值(线程安全的访问)。

三、典型使用场景

倒计时门闩常用于以下同步场景:

  1. 主线程等待子线程初始化:例如,主线程启动 N 个子线程后,调用wait()阻塞,每个子线程初始化完成后调用countDown(),当所有子线程初始化完毕(count_归 0),主线程被唤醒继续执行。
  2. 协调多个线程完成任务:例如,多个线程完成各自任务后调用countDown(),最后一个线程完成时唤醒等待的线程进行汇总操作。

四、核心设计思想

  • 线程同步:通过互斥锁(mutex_)保护共享变量count_,通过条件变量(cond_)实现线程间的等待 / 唤醒。
  • 计数器机制:用count_跟踪待完成的操作数量,归 0 时触发同步点。
  • 安全性:避免了多线程并发修改计数器的竞态条件,且通过循环判断防止条件变量的虚假唤醒。

3. 为什么需要reserve?

3.1 移动后的状态不确定性

std::vector<int> currentBuffer_(BufMaxLen);
// ... 填充数据// 移动后状态不确定
buffers_.push_back(std::move(currentBuffer_));// 此时 currentBuffer_ 可能是:
// 情况1: size=0, capacity=0 (需要重新分配)
// 情况2: size=0, capacity=BufMaxLen (理想情况)
// 情况3: 其他未指定状态// 为保证一致性,显式reserve
currentBuffer_.reserve(BufMaxLen);

std::move后原来的空间确实可能丢失,这正是需要reserve的关键原因。

1. std::move后的不确定性

标准规定:

  • 被移动后的对象处于有效但未指定状态

  • 实现可以自由选择如何处置被移动的对象

实际可能的情况:

std::vector<int> currentBuffer_(1000); // 容量1000// 移动操作后,currentBuffer_ 可能:
auto movedBuffer = std::move(currentBuffer_);// 情况1: 容量清零(常见实现)
// currentBuffer_.capacity() == 0// 情况2: 容量保留(某些优化)
// currentBuffer_.capacity() == 1000// 情况3: 其他任意有效状态

2. 具体验证代码

#include <vector>
#include <iostream>void demonstrateMoveUncertainty() {std::vector<int> buffer(1000, 42); // 容量1000std::cout << "移动前 - Size: " << buffer.size() << ", Capacity: " << buffer.capacity() << std::endl;std::vector<int> newBuffer = std::move(buffer);std::cout << "移动后 - Size: " << buffer.size() << ", Capacity: " << buffer.capacity() << std::endl;// 不同编译器的可能输出:// GCC:   移动后 - Size: 0, Capacity: 0// Clang: 移动后 - Size: 0, Capacity: 0  // MSVC:  移动后 - Size: 0, Capacity: 1000 (可能保留)
}

3. 为什么空间会丢失?

移动语义的实现选择:

// vector移动构造函数的可能实现之一
vector(vector&& other) noexcept : size_(other.size_), capacity_(other.capacity_), data_(other.data_) 
{// 标准允许:可以清零原对象other.size_ = 0;other.capacity_ = 0;  // 这里可能清零容量!other.data_ = nullptr;
}// 或者另一种实现:
vector(vector&& other) noexcept : size_(other.size_), capacity_(other.capacity_) , data_(other.data_)
{// 也可能保留原对象的容量other.size_ = 0;// other.capacity_ 保持不变other.data_ = nullptr;
}

http://www.dtcms.com/a/430611.html

相关文章:

  • 自监督学习在医疗AI中的技术实现路径分析(中)
  • QoS之拥塞管理两种配置方法
  • tp框架做网站的优点郑州品牌策划设计公司
  • 浅析 AC 自动机
  • Docker常见问题与解决
  • Rokid手势识别技术深度剖析
  • java web搭建商城购物车
  • 从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 6--基础知识 2--常用元素定位 2
  • 从“端到端”到“人到人”:一种以需求直接满足为核心的新一代人机交互范式
  • C到C++(Num015)
  • 做关于车的网站有哪些网页布局的方式有哪些
  • 图漾相机C++语言---Sample_V1(4.X.X版本)完整参考例子(待完善)
  • Python数据挖掘之基础分类模型_支持向量机(SVM)
  • Java-Spring 入门指南(十六)SpringMVC--RestFul 风格
  • 益阳网站制作公司地址高端装饰公司网站设计
  • 产生式规则在自然语言处理深层语义分析中的演变、影响与未来启示
  • K230基础-摄像头的使用
  • 【文件读写】绕过验证下
  • 谷歌官方网站注册12306铁路网站开发语言
  • 深度学习基础知识-深度神经网络基础
  • pycharm找不到Tencent Cloud CodeBuddy如何安装[windows]?pycharm插件市场找不到插件如何安装?
  • 【开题答辩全过程】以 SpringbootVueUniapp农产品展销平台为例,包含答辩的问题和答案
  • C++中的小数及整数位填充
  • DuckDB 的postgresql插件无法访问GooseDB
  • 电子商务软件网站建设的核心网站布局模板
  • 从Nginx到Keepalived:反向代理高可用的技术闭环——Nginx、Keepalived、VIP与VRRP的深度联动解析
  • 现场运维指南
  • 查看和修改Linux的主机名称
  • Vmware虚拟机联网问题,显示:线缆已拔出!!!
  • 部署Nginx(Kylinv10sp3、Ubuntu2204、Rocky9.3)