文章目录
Thread Cache 内存管理机制
一、整体架构设计
Thread Cache 是一个线程本地内存缓存系统,采用哈希桶 + 自由链表的复合结构:
哈希桶数组:包含多个桶位,每个桶位对应特定范围的内存大小
自由链表:每个桶位维护一个自由链表,管理相同大小的内存块
线程局部性:每个线程拥有独立的 Thread Cache 实例,实现无锁操作
二、内存申请详细流程
步骤1:请求分析与路径选择
当内存申请请求到达时,首先判断请求大小:
如果申请大小 > 256KB:直接交由系统内存分配器处理
如果申请大小 ≤ 256KB:进入 Thread Cache 分配流程
步骤2:Thread Cache 分配过程
获取线程本地缓存:通过线程局部存储(TLS)快速获取当前线程的 Thread Cache 对象实例
计算桶位索引:
根据申请的内存大小,通过特定算法计算出对应的哈希桶下标 i
计算方式通常为:index = f(size),其中 f 是大小到桶位的映射函数
检查自由链表:
访问 _freeLists[i] 自由链表,检查是否有可用内存块
如果链表不为空:直接从链表头部 Pop 一个内存块对象,立即返回给申请者
如果链表为空:进入批量获取流程
批量填充机制:
当自由链表为空时,向 Central Cache 发起批量申请
一次性获取多个相同大小的内存块(如20-50个)
将获取到的内存块插入到当前自由链表中
从新填充的链表中取出一个内存块返回给申请者
三、内存释放详细流程
步骤1:释放路径判断
当释放内存块时:
如果释放大小 > 256KB:直接返还给系统内存管理器
如果释放大小 ≤ 256KB:进入 Thread Cache 回收流程
步骤2:Thread Cache 回收过程
定位目标桶位:
根据释放的内存块大小,计算对应的哈希桶下标 i
确保计算方式与申请时保持一致
插入自由链表:
将释放的内存块 Push 到 _freeLists[i] 自由链表的头部
操作完成后立即返回,无需等待或同步
内存回收机制:
定期或在特定时机检查各自由链表的长度
当某个链表的长度超过预设阈值(如50个对象)时:
将该链表中部分内存块(如20个)回收到 Central Cache
保持链表中留有适当数量的对象供后续使用
这种机制防止单个线程占用过多内存,实现内存的全局平衡
四、技术优势与特点
1. 无锁高性能
线程局部存储:每个线程操作自己的缓存,完全避免锁竞争
快速路径:大多数分配操作只需操作链表指针,时间复杂度为 O(1)
低冲突:哈希桶设计减少不同大小内存块之间的干扰
2. 智能内存管理
批量操作:减少与 Central Cache 的交互次数,提高效率
动态平衡:自动的内存回收机制防止内存浪费
大小分类:不同范围的内存块使用不同的管理策略
五、TLS(Thread Local Storage)介绍
一、基本概念
TLS(Thread Local Storage,线程局部存储) 是一种允许每个线程拥有独立数据副本的机制。每个线程都可以读写自己的数据副本,而不会影响其他线程的数据。
二、C++ 中的 TLS 实现
- C++11 标准方式(推荐)
// 使用 thread_local 关键字
thread_local int thread_specific_data = 0;
thread_local std::string thread_name = "unknown";
thread_local MyClass thread_object;
// 线程局部指针
thread_local MyClass* thread_ptr = nullptr;
- 传统平台特定方式
// Windows API
__declspec(thread) int tls_data;
// GCC/Clang
__thread int tls_data;
// POSIX pthread
pthread_key_t key;
void* tls_data = pthread_getspecific(key);
三、TLS 的工作原理
内存布局
进程内存空间:
├── 代码段
├── 数据段
├── 堆段
├── 栈段(每个线程独立)
└── TLS 区域(每个线程有独立副本)
访问机制
// 编译器将 TLS 访问转换为特殊指令
thread_local int x = 42;
int get_value() {
return x; // 编译器生成 TLS 访问代码
}
四、TLS 的主要用途
- 线程特定的缓存(
这个就是要用的
)
// 每个线程有自己的内存缓存
thread_local ThreadCache* pTLSThreadCache = nullptr;
class ThreadCache {
public:
static ThreadCache* GetInstance() {
if (pTLSThreadCache == nullptr) {
pTLSThreadCache = new ThreadCache();
}
return pTLSThreadCache;
}
void* Allocate(size_t size) {
// 线程安全的无锁分配
return free_lists[SizeClass::Index(size)].Pop();
}
};
- 线程上下文信息
// 存储线程相关的上下文信息
thread_local ThreadContext ctx;
struct ThreadContext {
int thread_id;
std::string name;
std::chrono::steady_clock::time_point start_time;
// 其他线程特定数据
};
- 错误代码存储
// 类似 errno 的线程安全实现
thread_local int last_error_code = 0;
void set_error(int code) {
last_error_code = code;
}
int get_error() {
return last_error_code;
}
五、TLS 的优势
- 性能优势(
重点,减少了锁的使用
)
// 无锁访问示例
thread_local std::vector<int> local_cache;
void process_data(int data) {
// 无需锁,因为每个线程有自己的cache
local_cache.push_back(data);
if (local_cache.size() > 1000) {
flush_cache(local_cache);
local_cache.clear();
}
}
- 简化编程模型
// 无需维护线程ID到数据的映射表
class ThreadLogger {
private:
thread_local static std::ostringstream log_stream;
public:
static void log(const std::string& message) {
log_stream << message << std::endl;
}
static void flush() {
std::cout << log_stream.str();
log_stream.str("");
}
};
六、TLS 的局限性及注意事项
- 内存使用
// 每个线程都会有一份数据副本
thread_local std::array<char, 1024> buffer; // 每个线程消耗1KB
// 大量线程时可能消耗较多内存
constexpr int MAX_THREADS = 1000;
// 总内存使用: 1000 * 1KB = 1MB
- 初始化问题
// 动态初始化可能有问题
thread_local std::vector<int> data(1000); // 可能在线程启动时初始化
// 解决方案:延迟初始化
thread_local std::unique_ptr<std::vector<int>> data_ptr;
std::vector<int>& get_data() {
if (!data_ptr) {
data_ptr = std::make_unique<std::vector<int>>(1000);
}
return *data_ptr;
}
- 析构顺序
// TLS 变量的析构顺序不确定
thread_local ObjectA a;
thread_local ObjectB b; // 析构顺序:可能是b先,也可能是a先
// 有依赖关系时会有问题
thread_local std::shared_ptr<Resource> resource;
thread_local CleanupHandler cleanup; // 可能先于resource析构
Thread Cache 框架代码初步实现
一、common通用类实现
#pragma once // 防止头文件被重复包含
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include "Log.hpp" // 日志模块头文件
#include "Lockguard.hpp" // 锁保护模块头文件
using std::cout;
using std::endl;
using namespace LogModule; // 使用日志模块的命名空间
using namespace LockGuardModule; // 使用锁保护模块的命名空间
// 自由链表的总数量:208个桶,对应不同大小的内存块
static constexpr size_t NFREELISTS = 208;
// 线程缓存能处理的最大内存大小:256KB
// 超过这个大小的内存申请直接走中央缓存或页堆
static constexpr size_t MAX_BYTES = 256 * 1024;
/**
* @brief 获取内存块中的下一个对象指针
* @param ptr 当前内存块的指针引用
* @return void*& 下一个对象的指针引用
*
* 这个函数用于实现自由链表的连接功能。它将内存块的前8字节(在64位系统)
* 用作存储下一个内存块的地址,实现链式结构。
*
* 工作原理:
* 1. 将ptr强制转换为void**(指向指针的指针)
* 2. 解引用得到void*&(指针的引用)
* 3. 这样就可以直接修改下一个节点的地址
*/
inline void*& NextObj(void*& ptr)
{
return *static_cast<void**>(ptr);
}
/**
* @brief 不可拷贝的基类
*
* 这个类提供了禁止拷贝语义的基础功能,同时允许移动语义。
* 常用于需要防止意外拷贝的资源管理类(如单例、资源句柄等)。
*
* 设计特点:
* 1. 保护构造函数:只能被派生类访问
* 2. 删除拷贝构造函数和拷贝赋值运算符:防止拷贝
* 3. 默认移动构造函数和移动赋值运算符:允许移动
* 4. 虚析构函数:确保正确的多态销毁
*/
class NonCopyable
{
protected:
// 保护构造函数,只能被派生类实例化
NonCopyable() = default;
// 虚析构函数,确保派生类正确析构
virtual ~NonCopyable() = default;
// 删除拷贝操作,防止对象拷贝
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
// 允许移动操作,支持资源转移
NonCopyable(NonCopyable&&) = default;
NonCopyable& operator=(NonCopyable&&) = default;
};
二、sizeclass类的实现
这个类是一个高性能内存分配器中线程缓存(Thread Cache)的设计方案,主要目的是在最多10%内部碎片浪费的前提下,实现高效的内存管理。
一、设计核心思想
1. 分层管理
// 内存分配层次结构
Thread Cache → Central Cache → Page Heap
Thread Cache:线程本地缓存,无锁操作
Central Cache:中央缓存,线程间共享
Page Heap:页堆,直接管理操作系统内存
2. 大小分级策略
将内存请求按大小分为5个区间,每个区间采用不同的对齐方式和管理策略。
二、详细分区解析
分区1:小内存块 [1, 128] 字节
// 8字节对齐,16个自由链表桶
// 覆盖的大小:8,16,24,32,...,128字节
// 计算方式:ceil(size/8) * 8
// 碎片率:最多浪费7字节(12.5%),平均3.5字节(4.4%)
分区2:中小内存块 [129, 1024] 字节
// 16字节对齐,56个自由链表桶
// 覆盖的大小:144,160,176,...,1024字节
// 计算方式:ceil((size-128)/16) * 16 + 128
// 碎片率:最多浪费15字节(1.5%),平均7.5字节(0.7%)
分区3:中等内存块 [1025, 8*1024] 字节
// 128字节对齐,56个自由链表桶
// 覆盖的大小:1152,1280,1408,...,8192字节
// 计算方式:ceil((size-1024)/128) * 128 + 1024
// 碎片率:最多浪费127字节(1.6%),平均63.5字节(0.8%)
分区4:大内存块 [81024+1, 641024] 字节
// 1024字节对齐,56个自由链表桶
// 覆盖的大小:9K,10K,11K,...,64K
// 计算方式:ceil((size-8*1024)/1024) * 1024 + 8*1024
// 碎片率:最多浪费1023字节(1.6%),平均511.5字节(0.8%)
分区5:超大内存块 [641024+1, 2561024] 字节
// 8KB对齐,24个自由链表桶
// 覆盖的大小:72K,80K,88K,...,256K
// 计算方式:ceil((size-64*1024)/8192) * 8192 + 64*1024
// 碎片率:最多浪费8191字节(3.2%),平均4095.5字节(1.6%)
三、数学原理:控制碎片在10%以内
碎片计算公式
对于每个区间 [base, base + range] 和对齐大小 align:
最大碎片浪费 = align - 1 字节
平均碎片浪费 = align / 2 字节
最大碎片率 = (align - 1) / (base + 1) * 100%
实际计算示例
对于72字节的请求(在第二区间)
请求大小:72字节
对齐后:ceil((72-128)/16)*16 + 128 = 144字节
浪费:144 - 72 = 72字节(但实际算法不同)
修正:第二区间实际从129开始,所以:
实际计算:ceil(72/16)*16 = 80字节(如果单独处理)
四、技术实现代码(部分)
大小到索引的映射函数
class SizeClass {
public:
// 计算内存大小对应的自由链表索引
static size_t Index(size_t size) {
if (size <= 128) {
return _Index(size, 3); // 8字节对齐:2^3=8
} else if (size <= 1024) {
return _Index(size - 128, 4) + 16; // 16字节对齐
} else if (size <= 8*1024) {
return _Index(size - 1024, 7) + 16 + 56; // 128字节对齐
} else if (size <= 64*1024) {
return _Index(size - 8*1024, 10) + 16 + 56 + 56; // 1024字节对齐
} else {
return _Index(size - 64*1024, 13) + 16 + 56 + 56 + 56; // 8192字节对齐
}
}
private:
// 对齐计算辅助函数
static size_t _Index(size_t size, size_t align_shift) {
size_t align = 1 << align_shift;
return ((size + align - 1) >> align_shift) - 1;
}
};
线程缓存数据结构
class ThreadCache {
private:
FreeList free_lists_[208]; // 总共208个自由链表桶
// 桶数量分布
// [0,16) : 16个桶 - 8字节对齐
// [16,72) : 56个桶 - 16字节对齐
// [72,128) : 56个桶 - 128字节对齐
// [128,184) : 56个桶 - 1024字节对齐
// [184,208) : 24个桶 - 8192字节对齐
};
五、性能优势
1. 无锁操作
// 每个线程有自己的ThreadCache实例
static thread_local ThreadCache* tls_thread_cache = nullptr;
void* Allocate(size_t size) {
size_t index = SizeClass::Index(size);
return free_lists_[index].Pop(); // 无锁操作
}
2. 内存利用率高
小内存:8字节对齐,减少浪费
大内存:较大的对齐步长,减少桶数量
整体:控制碎片率在10%以内
3. 快速分配
时间复杂度:O(1) - 直接数组索引访问
缓存友好:连续内存访问模式
批量操作:减少与Central Cache的交互
完全代码:
#pragma once
#include "common.h"
/**
* 整体控制在最多10%左右的内碎⽚浪费
* [1,128] 8byte对⻬ freelist[0,16) 16
* [128+1,1024] 16byte对⻬ freelist[16,72) 56
* [1024+1,8*1024] 128byte对⻬ freelist[72,128) 56
* [8*1024+1,64*1024] 1024byte对⻬ freelist[128,184) 56
* [64*1024+1,256*1024] 8*1024byte对⻬ freelist[184,208) 24
*/
//对齐规则
class SizeClass
{
public:
static inline int RoundUp(size_t bytes)
{
if(betweenNum(bytes, 1, 128))
{
return _RoundUp(bytes, 8);
}
else if(betweenNum(bytes, 128+1, 1024))
{
return _RoundUp(bytes, 16);
}
else if(betweenNum(bytes, 1024+1, 8*1024))
{
return _RoundUp(bytes, 128);
}
else if(betweenNum(bytes, 8*1024+1, 64*1024))
{
return _RoundUp(bytes, 1024);
}
else if(betweenNum(bytes, 64*1024+1, 256*1024))
{
return _RoundUp(bytes, 1024*8);
}
else
{
Log(WARNING) << "size invalid!";
return -1;
}
}
static inline int Index(size_t bytes)
{
std::vector<int> groupnum = {16, 56, 56, 56, 14};
if(betweenNum(bytes, 1, 128))
{
return _Index(bytes, 3);
}
else if(betweenNum(bytes, 128+1, 1024))
{
return _Index(bytes-128, 4) + groupnum[0];
}
else if(betweenNum(bytes, 1024+1, 8*1024))
{
return _Index(bytes-1024, 7) + groupnum[0] + groupnum[1];
}
else if(betweenNum(bytes, 8*1024+1, 64*1024))
{
return _Index(bytes-8*1024, 10) + groupnum[0] + groupnum[1] + groupnum[2];
}
else if(betweenNum(bytes, 64*1024+1, 256*1024))
{
return _Index(bytes-64*1024, 13) + groupnum[0] + groupnum[1] + groupnum[2] + groupnum[3];
}
else
{
Log(WARNING) << "size invalid!";
return -1;
}
}
private:
static size_t _RoundUp(size_t bytes, size_t align)
{
//将 bytes 向上取整到最接近的 align(必须是2的幂次方) 的倍数
return (bytes + align - 1) & (~(align - 1));
}
static size_t _Index(size_t bytes, size_t align_shift)
{
//等价于:ceil(bytes / alignment) - 1 下标从0开始
return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;
}
static bool betweenNum(int num, int low, int high)
{
return (num >= low && num < high) ? true : false;
}
};
三、freelist链表类实现
#pragma once // 防止头文件被重复包含
#include "common.h" // 包含通用定义
/**
* @brief 自由链表类,用于管理固定大小的内存块
*
* 自由链表是内存池的核心组件,用于高效地分配和回收固定大小的内存块。
* 每个FreeList实例管理一种特定大小的内存块。
*/
class FreeList : public NonCopyable
{
public:
/**
* @brief 构造函数,初始化自由链表
*
* 初始化一个空的自由链表,设置初始最大批量和当前大小为0。
*/
FreeList()
: _freelist(nullptr) // 链表头指针初始化为空
, _maxsize(1) // 初始最大批量大小为1
, _size(0) // 当前链表中的内存块数量为0
{}
/**
* @brief 将内存块压入自由链表(回收内存)
* @param ptr 要回收的内存块指针
*
* 操作步骤:
* 1. 检查指针有效性
* 2. 将新内存块插入链表头部
* 3. 更新链表大小计数
*
* 链表结构变化:
* 原链表: A → B → C → nullptr
* 插入X后: X → A → B → C → nullptr
*/
void Push(void* ptr)
{
if(!ptr) // 安全检查:空指针检查
{
Log(WARNING) << "ptr is null"; // 记录警告日志
return;
}
NextObj(ptr) = _freelist; // 将新块的下一个指针指向当前链表头
_freelist = ptr; // 更新链表头为新块
_size++; // 增加链表大小计数
}
/**
* @brief 从自由链表弹出内存块(分配内存)
* @return void* 分配的内存块指针,如果链表为空返回nullptr
*
* 操作步骤:
* 1. 检查链表是否为空
* 2. 移除链表头部的内存块
* 3. 更新链表大小计数
* 4. 返回分配的内存块
*
* 链表结构变化:
* 原链表: A → B → C → nullptr
* 弹出A后: B → C → nullptr
*/
void* Pop()
{
if(!_freelist) // 检查链表是否为空
{
Log(WARNING) << "freelist is null"; // 记录警告日志
return nullptr;
}
void* ret = _freelist; // 保存要返回的内存块
_freelist = NextObj(_freelist); // 将链表头指向下一个块
_size--; // 减少链表大小计数
return ret; // 返回分配的内存块
}
/**
* @brief 检查自由链表是否为空
* @return bool true表示链表为空,false表示有可用内存块
*/
bool Empty()
{
return _size == 0; // 通过大小计数判断是否为空
}
/**
* @brief 获取当前设置的最大批量大小
* @return size_t 最大批量大小值
*
* 这个值用于控制从中央缓存批量获取内存块的数量:
* - 当链表空时,一次性获取_maxsize个内存块
* - 动态调整策略可以根据使用频率优化这个值
*/
size_t Maxsize()
{
return _maxsize;
}
/**
* @brief 获取当前链表中内存块的数量
* @return size_t 当前链表中的内存块数量
*
* 用于监控和统计:
* - 判断是否需要向中央缓存申请更多内存块
* - 判断是否需要归还多余内存块到中央缓存
*/
size_t Size()
{
return _size;
}
/**
* @brief 析构函数
*
* 使用默认析构函数,因为:
* 1. FreeList只管理内存块指针,不负责内存块的分配和释放
* 2. 实际的内存管理由上层的内存池组件负责
* 3. 链表中的内存块会在中央缓存或页堆中被最终释放
*/
~FreeList() = default;
private:
void* _freelist; // 链表头指针,指向第一个空闲内存块
size_t _maxsize; // 最大批量大小,控制从中央缓存获取内存块的数量
size_t _size; // 当前链表中内存块的数量
// 注意:这个类没有拷贝构造函数和赋值运算符
// 因为自由链表通常不应该被拷贝,每个FreeList管理特定的内存块集合
};
四、threadcache类实现框架
#pragma once // 防止头文件被重复包含
#include "common.h" // 通用定义和工具函数
#include "FreeList.hpp" // 自由链表实现
#include "SizeClass.hpp" // 大小分类和对齐计算
/**
* @brief 线程缓存类,每个线程独有的内存缓存
*
* ThreadCache 是内存分配器的第一层,每个线程拥有独立的实例,
* 用于快速分配和释放小内存块,避免多线程竞争和锁开销。
* 继承自 NonCopyable 确保线程缓存不能被拷贝。
*/
class ThreadCache : public NonCopyable
{
public:
/**
* @brief 构造函数
*
* 初始化线程缓存,创建NFREELISTS个自由链表桶。
* 每个桶管理特定大小的内存块。
*/
ThreadCache()
{}
/**
* @brief 内存分配接口
* @param size 请求的内存大小
* @return void* 分配的内存块指针,失败返回nullptr
*
* 分配流程:
* 1. 参数校验:大小是否在有效范围内
* 2. 计算对齐后的大小和对应的自由链表索引
* 3. 如果对应自由链表不为空,直接弹出内存块返回
* 4. 如果自由链表为空,从中央缓存批量获取内存块
*/
void* Allocate(size_t size)
{
void* obj = nullptr;
// 参数校验:只处理256KB以内的内存请求
if(size <= 0 || size > MAX_BYTES)
{
Log(WARNING) << "size invalid!";
return obj;
}
// 计算对齐后的大小和对应的桶索引
int alignsize = SizeClass::RoundUp(size); // 向上对齐到合适的大小
int index = SizeClass::Index(size); // 计算对应的自由链表索引
// 如果当前自由链表有可用内存块,直接分配
if(!_freelists[index].Empty())
{
obj = _freelists[index].Pop(); // 从链表头部弹出内存块
return obj;
}
else
{
// TODO: 从中央缓存批量获取内存块
// 需要实现FetchFromCentralCache方法
return FetchFromCentralCache(index, alignsize);
}
}
/**
* @brief 内存释放接口
* @param ptr 要释放的内存块指针
* @param size 内存块的大小
*
* 释放流程:
* 1. 参数校验:指针和大小是否有效
* 2. 计算对应的自由链表索引
* 3. 将内存块压入对应的自由链表
* 4. 如果链表过长,归还部分内存到中央缓存
*/
void Deallocate(void* ptr, size_t size)
{
// 参数校验
if(size <= 0 || size > MAX_BYTES || ptr == nullptr)
{
Log(WARNING) << "size invalid or ptr is null!";
return;
}
// 计算对应的自由链表索引
int index = SizeClass::Index(size);
// 将内存块回收至自由链表
_freelists[index].Push(ptr);
// TODO: 检查链表长度,如果过长则归还部分内存到中央缓存
// 防止单个线程占用过多内存
if(_freelists[index].Size() >= _freelists[index].Maxsize() * 2)
{
ListTooLong(_freelists[index], size);
}
}
/**
* @brief 从中央缓存获取内存块
* @param index 自由链表索引
* @param size 需要的内存块大小
* @return void* 获取到的内存块指针
*
* 需要实现的功能:
* 1. 根据当前需求和历史使用情况,计算批量获取的数量
* 2. 从中央缓存获取多个内存块
* 3. 将获取的内存块插入到自由链表
* 4. 返回其中一个内存块给申请者
*/
void* FetchFromCentralCache(size_t index, size_t size)
{
// TODO: 实现从中央缓存批量获取内存块的逻辑
return nullptr;
}
/**
* @brief 处理过长的自由链表
* @param list 需要处理的自由链表引用
* @param size 内存块大小
*
* 需要实现的功能:
* 1. 当自由链表中的内存块数量超过阈值时
* 2. 将部分内存块归还给中央缓存
* 3. 保持适当的缓存大小,避免内存浪费
*/
void ListTooLong(FreeList& list, size_t size)
{
// TODO: 实现内存块归还逻辑
}
/**
* @brief 析构函数
*
* 线程退出时,ThreadCache析构函数会被调用。
* 需要确保将所有内存块归还给中央缓存。
*/
~ThreadCache()
{
// TODO: 遍历所有自由链表,归还所有内存块到中央缓存
}
private:
FreeList _freelists[NFREELISTS]; // 自由链表数组,共208个桶
};
/**
* @brief 线程局部存储的ThreadCache指针
*
* 关键特性:
* - static: 静态存储期,整个程序生命周期存在
* - thread_local: 每个线程有独立的副本,实现无锁访问
* - ThreadCache*: 指向线程缓存对象的指针
* - = nullptr: 初始化为空指针,延迟初始化
*
* 这是高性能内存分配器的核心技术:
* 1. 每个线程有自己的内存缓存,避免锁竞争
* 2. 通过TLS机制实现快速访问
* 3. 延迟初始化,只在需要时创建ThreadCache实例
*/
static thread_local ThreadCache* pTLSThreadCache = nullptr;
五、辅助类实现
警卫锁
#ifndef __LOCKGUARD_HPP__
#define __LOCKGUARD_HPP__
#include<pthread.h>
namespace LockGuardModule
{
class Lockguard
{
public:
Lockguard(pthread_mutex_t& mutex)
: _mutex(mutex)
{
pthread_mutex_lock(&_mutex);
}
~Lockguard()
{
pthread_mutex_unlock(&_mutex);
}
private:
pthread_mutex_t& _mutex;
};
}
#endif
锁
#ifndef __MUTEX_HPP__
#define __MUTEX_HPP__
#include<pthread.h>
#include<unistd.h>
#include<iostream>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
pthread_mutex_lock(&_mutex);
}
void Unlock()
{
pthread_mutex_unlock(&_mutex);
}
pthread_mutex_t& Getmutex()
{
return _mutex;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex;
};
}
#endif
日志
#ifndef __LOG_HPP__
#define __LOG_HPP__
#include <iostream>
#include <ctime>
#include <string>
#include <pthread.h>
#include <sstream>
#include <fstream>
#include <filesystem>
#include <unistd.h>
#include <memory>
#include "Mutex.hpp"
namespace LogModule
{
using namespace MutexModule;
std::string default_path = "./log/";
std::string default_file = "log.txt";
enum LogLevel
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string LogLevelToString(LogLevel level)
{
switch (level)
{
case DEBUG:
return "DEBUG";
case INFO:
return "INFO";
case WARNING:
return "WARNING";
case ERROR:
return "ERROR";
case FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
std::string GetCurrentTime()
{
std::time_t time = std::time(nullptr);
struct tm stm;
localtime_r(&time, &stm);
char buff[128];
snprintf(buff, sizeof(buff), "%4d-%02d-%02d-%02d-%02d-%02d",
stm.tm_year + 1900,
stm.tm_mon + 1,
stm.tm_mday,
stm.tm_hour,
stm.tm_min,
stm.tm_sec);
return buff;
}
class Logstrategy
{
public:
virtual ~Logstrategy() = default;
virtual void syncLog(std::string &message) = 0;
};
class ConsoleLogstrategy : public Logstrategy
{
public:
void syncLog(std::string &message) override
{
std::cerr << message << std::endl;
}
~ConsoleLogstrategy() override
{
}
};
class FileLogstrategy : public Logstrategy
{
public:
FileLogstrategy(std::string filepath = default_path, std::string filename = default_file)
{
_mutex.Lock();
_filepath = filepath;
_filename = filename;
if (std::filesystem::exists(filepath)) // 检测目录是否存在,存在则返回
{
_mutex.Unlock();
return;
}
try
{
// 不存在则递归创建(复数)目录
std::filesystem::create_directories(filepath);
}
catch (const std::filesystem::filesystem_error &e)
{
// 捕获异常并打印
std::cerr << e.what() << '\n';
}
_mutex.Unlock();
}
void syncLog(std::string &message) override
{
_mutex.Lock();
std::string path =
_filepath.back() == '/' ? _filepath + _filename : _filepath + "/" + _filename;
std::ofstream out(path, std::ios::app);
if (!out.is_open())
{
_mutex.Unlock();
std::cerr << "file open fail!" << '\n';
return;
}
out << message << '\n';
_mutex.Unlock();
out.close();
}
~FileLogstrategy()
{
}
private:
std::string _filepath;
std::string _filename;
Mutex _mutex;
};
class Log
{
public:
Log()
{
_logstrategy = std::make_unique<ConsoleLogstrategy>();
}
void useconsolestrategy()
{
_logstrategy = std::make_unique<ConsoleLogstrategy>();
printf("转换控制台策略!\n");
}
void usefilestrategy()
{
_logstrategy = std::make_unique<FileLogstrategy>();
printf("转换文件策略!\n");
}
class LogMessage
{
public:
LogMessage(LogLevel level, std::string file, int line, Log &log)
: _loglevel(level)
, _time(GetCurrentTime())
, _file(file), _pid(getpid())
, _line(line),
_log(log)
{
std::stringstream ss;
ss << "[" << _time << "] "
<< "[" << LogLevelToString(_loglevel) << "] "
<< "[" << _pid << "] "
<< "[" << _file << "] "
<< "[" << _line << "] "
<< "- ";
_loginfo = ss.str();
}
template <typename T>
LogMessage &operator<<(const T &t)
{
std::stringstream ss;
ss << _loginfo << t;
_loginfo = ss.str();
//printf("重载<<Logmessage!\n");
return *this;
}
~LogMessage()
{
//printf("析构函数\n");
if (_log._logstrategy)
{
//printf("调用打印.\n");
_log._logstrategy->syncLog(_loginfo);
}
}
private:
LogLevel _loglevel;
std::string _time;
pid_t _pid;
std::string _file;
int _line;
std::string _loginfo;
Log &_log;
};
LogMessage operator()(LogLevel level, std::string filename, int line)
{
return LogMessage(level, filename, line, *this);
}
~Log()
{
}
private:
std::unique_ptr<Logstrategy> _logstrategy;
};
Log logger;
#define Log(type) logger(type, __FILE__, __LINE__)
#define ENABLE_LOG_CONSOLE_STRATEGY() logger.useconsolestrategy()
#define ENABLE_LOG_FILE_STRATEGY() logger.usefilestrategy()
}
#endif