异步日志系统

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 次数:缓冲区满或空间不足时才切换,实现批量写入,提升效率。
cpp 复制代码
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;  // 计数器减1
    if(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 移动后的状态不确定性

cpp 复制代码
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后的不确定性

标准规定:

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

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

实际可能的情况:

cpp 复制代码
std::vector<int> currentBuffer_(1000); // 容量1000

// 移动操作后,currentBuffer_ 可能:
auto movedBuffer = std::move(currentBuffer_);

// 情况1: 容量清零(常见实现)
// currentBuffer_.capacity() == 0

// 情况2: 容量保留(某些优化)
// currentBuffer_.capacity() == 1000

// 情况3: 其他任意有效状态

2. 具体验证代码

cpp 复制代码
#include <vector>
#include <iostream>

void demonstrateMoveUncertainty() {
    std::vector<int> buffer(1000, 42); // 容量1000
    std::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. 为什么空间会丢失?

移动语义的实现选择:

cpp 复制代码
// 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;
}
相关推荐
珹洺8 小时前
Java-Spring入门指南(十九)thymeleaf基本概念
java·spring·状态模式
charlie1145141912 天前
精读 C++20 设计模式:行为型设计模式 — 状态机模式
c++·学习·设计模式·状态模式·c++20
i小杨3 天前
前端埋点(打点)方案
前端·状态模式
大飞pkz4 天前
【设计模式】状态模式
开发语言·设计模式·c#·状态模式
PaoloBanchero5 天前
Unity 虚拟仿真实验中设计模式的使用 ——状态模式(State Pattern)
unity·设计模式·状态模式
float_六七5 天前
Nginx反向代理核心原理揭秘
运维·nginx·状态模式
宁雨桥6 天前
前端登录加密实战:从原理到落地,守护用户密码安全
前端·安全·状态模式
phdsky9 天前
【设计模式】状态模式
设计模式·状态模式
风槐啊9 天前
邪修实战系列(6)
java·ide·windows·spring boot·状态模式