LinuxC++项目开发日志——高并发内存池(3-thread cache框架开发)

发布于:2025-09-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

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 实现

  1. 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;
  1. 传统平台特定方式
// 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 的主要用途

  1. 线程特定的缓存(这个就是要用的)
// 每个线程有自己的内存缓存
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();
    }
};
  1. 线程上下文信息
// 存储线程相关的上下文信息
thread_local ThreadContext ctx;

struct ThreadContext {
    int thread_id;
    std::string name;
    std::chrono::steady_clock::time_point start_time;
    // 其他线程特定数据
};
  1. 错误代码存储
// 类似 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 的优势

  1. 性能优势(重点,减少了锁的使用)
// 无锁访问示例
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();
    }
}
  1. 简化编程模型
// 无需维护线程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 的局限性及注意事项

  1. 内存使用
// 每个线程都会有一份数据副本
thread_local std::array<char, 1024> buffer; // 每个线程消耗1KB

// 大量线程时可能消耗较多内存
constexpr int MAX_THREADS = 1000;
// 总内存使用: 1000 * 1KB = 1MB
  1. 初始化问题
// 动态初始化可能有问题
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;
}
  1. 析构顺序
// 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

网站公告

今日签到

点亮在社区的每一天
去签到