【lesson4】高并发内存池ThreadCache(线程缓存)层实现

文章目录

  • ThreadCache层的结构
  • 申请内存逻辑
  • 释放内存逻辑
  • 自由链表的实现
    • 自由链表的成员变量
    • 自由链表的成员函数
    • 自由链表的完整实现
  • ThreadCache申请内存过程的实现
    • ThreadCache需要的成员变量
    • ThreadCache需要的成员函数
    • ThreadCache.h文件代码
    • Allocate的实现
    • Deallocate的实现
  • 封装ThreadCache层可以多线程访问
  • ThreadCache层完整代码
    • Common.h
    • ThreadCache.h
    • ThreadCache.cpp
    • ConcurrentAlloc.h

ThreadCache层的结构

thread cache是哈希桶结构,每个桶是一个按桶位置映射大小的内存块对象的自由链表。每个线程都会有一个thread cache对象,这样每个线程在这里获取对象和释放对象时是无锁的。

在这里插入图片描述

申请内存逻辑

  1. 当内存申请size<=256KB,先获取到线程本地存储的thread cache对象,计算size映射的哈希桶自由链表下标i。
  2. 如果自由链表_freeLists[i]中有对象,则直接Pop一个内存对象返回。
  3. 如果_freeLists[i]中没有对象时,则批量从central cache中获取一定数量的对象,插入到自由链表并返回一个对象。

释放内存逻辑

释放逻辑之后再完整实现,现在先了解释放流程

  1. 当释放内存小于256k时将内存释放回thread cache,计算size映射自由链表桶位置i,将对象Push到_freeLists[i]。
  2. 当链表的长度过长,则回收一部分内存对象到central cache。

自由链表的实现

从上面我们可以知道,ThreadCache存放内存块需要自由链表,所以我们要先实现自由链表。

自由链表的成员变量

在这里插入图片描述
之前学过定长内存池就知道用一个_freeList指针就能管理好多个内存块。

自由链表的成员函数

在这里插入图片描述
Push:可以让内存块链入自由链表中
Pop:可以将内存块从自由链表中移除(也就是该内存块被申请出去了)
Empty:可以判断该自由链表是否为空。

Push的过程
在这里插入图片描述
Push的实现

void Push(void* obj){assert(obj);//断言保证obj不为空// 头插*(void**)obj = _freeList;_freeList = obj;}

因为*(void**)obj在后面很多地方都会用到,所以我们对其封装成函数

static void*& NextObj(void* obj)
{return *(void**)obj;
}
//这里用static的原因是因为,NextObj是
//被放进行Common.h文件的,而Common.h文件
//肯被对个.cpp文件包含,那么到时编译器就会
//报错
//而static修饰函数表示只在Common.h文件内有效

所以我们Push就可改成

void Push(void* obj){assert(obj);//断言保证obj不为空// 头插//*(void**)obj = _freeList;NextObj(obj) = _freeList;_freeList = obj;}

如果大家不理解*(void**)obj,可以去之前的博客观看,这里不再做讲解。

Pop的过程:
在这里插入图片描述
Pop的实现:

void* Pop(){assert(_freeList);//_freeList一定不能为空// 头删void* obj = _freeList;_freeList = NextObj(obj);return obj;}

Empty的实现

bool Empty(){return _freeList == nullptr;}

自由链表的完整实现

// 管理切分好的小对象的自由链表
class FreeList
{
public:void Push(void* obj){assert(obj);// 头插//*(void**)obj = _freeList;NextObj(obj) = _freeList;_freeList = obj;}void* Pop(){assert(_freeList);// 头删void* obj = _freeList;_freeList = NextObj(obj);return obj;}bool Empty(){return _freeList == nullptr;}
private:void* _freeList = nullptr;
};

ThreadCache申请内存过程的实现

首先做项目,我们肯定要定义一个包含头文件的.h文件一般命名为Common.h文件
Common.h

//Common.h
#pragma once
#include <iostream>
#include <assert.h>
using std::cout;
using std::endl;// thread cache 和 central cache自由链表哈希桶的表大小
static const size_t NFREELISTS = 208;static void*& NextObj(void* obj)
{return *(void**)obj;
}// 管理切分好的小对象的自由链表
class FreeList
{
public:void Push(void* obj){assert(obj);// 头插//*(void**)obj = _freeList;NextObj(obj) = _freeList;_freeList = obj;}void* Pop(){assert(_freeList);// 头删void* obj = _freeList;_freeList = NextObj(obj);return obj;}bool Empty(){return _freeList == nullptr;}
private:void* _freeList = nullptr;
};

这里我们先定义这几个,等其它有用到再慢慢添加。
然后实现ThreadCache也要定义两个文件。
一个是ThreadCache.h,ThreadCache.cpp
ThreadCache定义两个文件是为了声明和定义分离,这样更符合实际的项目场景。

ThreadCache.h

//ThreadCache.h
#include "Common.h"

ThreadCache.cpp

//ThreadCache.cpp
#include "ThreadCache.h"

ThreadCache需要的成员变量

在这里插入图片描述
从ThreadCache的结构中我们就可以知道,我们肯定要定义一个指针数组,而每个指针都用来管理内存块,所以这些指针都是自由链表指针

class ThreadCache
{
public:private:FreeList _freeLists[NFREELIST];
};

NFREELIST是thread cache自由链表哈希桶的表大小,为208个,所以我们还要Common.h中定义。

ThreadCache需要的成员函数

class ThreadCache
{
public:// 申请和释放内存对象void* Allocate(size_t size);void Deallocate(void* ptr, size_t size);// 从中心缓存获取对象void* FetchFromCentralCache(size_t index, size_t size);
private:FreeList _freeLists[NFREELIST];
};

Allocate:申请内存对象
Deallocate:释放内存对象
FetchFromCentralCache:哈希桶如果内存空间不够,向中心缓存(central cache)申请内存空间

ThreadCache.h文件代码

#pragma once#include "Common.h"class ThreadCache
{
public:// 申请和释放内存对象void* Allocate(size_t size);void Deallocate(void* ptr, size_t size);// 从中心缓存获取对象void* FetchFromCentralCache(size_t index, size_t size);
private:FreeList _freeLists[NFREELIST];
};

那么接下来就是实现这些成员函数了。

Allocate的实现

首先我们要知道申请的空间的字节数大小是多少。
其次我们要对该大小进行对齐,我们用自己设定的对齐规则对原字节数大小对齐
最后我们要根据字节数大小找到对应的哈希桶。

那么这是我们就要设计一个类帮助我们解决这些问题。

//放在common.h中
// 计算对象大小的对齐映射规则
class SizeClass
{
public://计算对象大小的对齐映射规则// 整体控制在最多10%左右的内碎片浪费// [1,128]					8byte对齐	    freelist[0,16)// [128+1,1024]				16byte对齐	    freelist[16,72)// [1024+1,8*1024]			128byte对齐	    freelist[72,128)// [8*1024+1,64*1024]		1024byte对齐     freelist[128,184)// [64*1024+1,256*1024]		8*1024byte对齐   freelist[184,208)//方法一:/*size_t _RoundUp(size_t size, size_t alignNum){size_t alignSize;if (size % alignNum != 0){alignSize = (size / alignNum + 1)*alignNum;}else{alignSize = size;}return alignSize;}*/// 1-8 //方法二:static inline size_t _RoundUp(size_t bytes, size_t alignNum){return ((bytes + alignNum - 1) & ~(alignNum - 1));}//字节对齐static inline size_t RoundUp(size_t size){if (size <= 128){return _RoundUp(size, 8);}else if (size <= 1024){return _RoundUp(size, 16);}else if (size <= 8*1024){return _RoundUp(size, 128);}else if (size <= 64*1024){return _RoundUp(size, 1024);}else if (size <= 256 * 1024){return _RoundUp(size, 8*1024);}else{assert(false);return -1;}}//方法一:/*size_t _Index(size_t bytes, size_t alignNum){if (bytes % alignNum == 0){return bytes / alignNum - 1;}else{return bytes / alignNum;}}*///方法二:static inline size_t _Index(size_t bytes, size_t align_shift){return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;}// 计算映射的哪一个自由链表桶static inline size_t Index(size_t bytes){assert(bytes <= MAX_BYTES);// 每个区间有多少个链static int group_array[4] = { 16, 56, 56, 56 };if (bytes <= 128){return _Index(bytes, 3);}else if (bytes <= 1024){return _Index(bytes - 128, 4) + group_array[0];}else if (bytes <= 8 * 1024){return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];}else if (bytes <= 64 * 1024){return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1] + group_array[0];}else if (bytes <= 256 * 1024){return _Index(bytes - 64 * 1024, 13) + group_array[3] + group_array[2] + group_array[1] + group_array[0];}else{assert(false);}return -1;}};

Allocate的实现

void* ThreadCache::Allocate(size_t size)
{//size是要申请空间的字节大小//MAX_BYTES为最大申请的空间字节数 256*1024字节assert(size <= MAX_BYTES);//首先计算出对齐字节数和对应桶的下标size_t alignSize = SizeClass::RoundUp(size);size_t index = SizeClass::Index(size);//如果对应桶不为空,则拿对应的字节空间if (!_freeLists[index].Empty()){return _freeLists[index].Pop();}else//如果对应桶为空,则向中心缓存申请空间{return FetchFromCentralCache(index, alignSize);}
}

Deallocate的实现

void ThreadCache::Deallocate(void* ptr, size_t size)
{assert(ptr);assert(size <= MAX_BYTES);// 找对映射的自由链表桶,对象插入进入size_t index = SizeClass::Index(size);_freeLists[index].Push(ptr);

封装ThreadCache层可以多线程访问

只要在ThreadCache.h文件添加下列代码,就可以支持多线程访问。

// TLS thread local storage
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

然后用ConcurrentAlloc.h封装ThreadCache层,不让外面直接可以访问到ThreadCache的函数。保证安全性

pragma once#include "Common.h"
#include "ThreadCache.h"static void* ConcurrentAlloc(size_t size)
{// 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象if (pTLSThreadCache == nullptr){pTLSThreadCache = new ThreadCache;}return pTLSThreadCache->Allocate(size);
}static void ConcurrentFree(void* ptr, size_t size)
{assert(pTLSThreadCache);pTLSThreadCache->Deallocate(ptr, size);
}

ThreadCache层完整代码

Common.h

#pragma once#include <iostream>
#include <vector>
#include <thread>
#include <time.h>
#include <assert.h>
using std::cout;
using std::endl;static const size_t MAX_BYTES = 256 * 1024;
static const size_t NFREELIST = 208;static void*& NextObj(void* obj)
{return *(void**)obj;
}// 管理切分好的小对象的自由链表
class FreeList
{
public:void Push(void* obj){assert(obj);// 头插//*(void**)obj = _freeList;NextObj(obj) = _freeList;_freeList = obj;}void* Pop(){assert(_freeList);// 头删void* obj = _freeList;_freeList = NextObj(obj);return obj;}bool Empty(){return _freeList == nullptr;}
private:void* _freeList = nullptr;
};// 计算对象大小的对齐映射规则
class SizeClass
{
public:// 整体控制在最多10%左右的内碎片浪费// [1,128]					8byte对齐	    freelist[0,16)// [128+1,1024]				16byte对齐	    freelist[16,72)// [1024+1,8*1024]			128byte对齐	    freelist[72,128)// [8*1024+1,64*1024]		1024byte对齐     freelist[128,184)// [64*1024+1,256*1024]		8*1024byte对齐   freelist[184,208)/*size_t _RoundUp(size_t size, size_t alignNum){size_t alignSize;if (size % alignNum != 0){alignSize = (size / alignNum + 1)*alignNum;}else{alignSize = size;}return alignSize;}*/// 1-8 static inline size_t _RoundUp(size_t bytes, size_t alignNum){return ((bytes + alignNum - 1) & ~(alignNum - 1));}static inline size_t RoundUp(size_t size){if (size <= 128){return _RoundUp(size, 8);}else if (size <= 1024){return _RoundUp(size, 16);}else if (size <= 8*1024){return _RoundUp(size, 128);}else if (size <= 64*1024){return _RoundUp(size, 1024);}else if (size <= 256 * 1024){return _RoundUp(size, 8*1024);}else{assert(false);return -1;}}/*size_t _Index(size_t bytes, size_t alignNum){if (bytes % alignNum == 0){return bytes / alignNum - 1;}else{return bytes / alignNum;}}*/// 1 + 7  8// 2      9// ...// 8      15// 9 + 7 16// 10// ...// 16    23static inline size_t _Index(size_t bytes, size_t align_shift){return ((bytes + (1 << align_shift) - 1) >> align_shift) - 1;}// 计算映射的哪一个自由链表桶static inline size_t Index(size_t bytes){assert(bytes <= MAX_BYTES);// 每个区间有多少个链static int group_array[4] = { 16, 56, 56, 56 };if (bytes <= 128){return _Index(bytes, 3);}else if (bytes <= 1024){return _Index(bytes - 128, 4) + group_array[0];}else if (bytes <= 8 * 1024){return _Index(bytes - 1024, 7) + group_array[1] + group_array[0];}else if (bytes <= 64 * 1024){return _Index(bytes - 8 * 1024, 10) + group_array[2] + group_array[1] + group_array[0];}else if (bytes <= 256 * 1024){return _Index(bytes - 64 * 1024, 13) + group_array[3] + group_array[2] + group_array[1] + group_array[0];}else{assert(false);}return -1;}};

ThreadCache.h

#pragma once#include "Common.h"class ThreadCache
{
public:// 申请和释放内存对象void* Allocate(size_t size);void Deallocate(void* ptr, size_t size);// 从中心缓存获取对象void* FetchFromCentralCache(size_t index, size_t size);
private:FreeList _freeLists[NFREELIST];
};// TLS thread local storage
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;

ThreadCache.cpp

#include "ThreadCache.h"void* ThreadCache::FetchFromCentralCache(size_t index, size_t size)
{// ...return nullptr;
}void* ThreadCache::Allocate(size_t size)
{assert(size <= MAX_BYTES);size_t alignSize = SizeClass::RoundUp(size);size_t index = SizeClass::Index(size);if (!_freeLists[index].Empty()){return _freeLists[index].Pop();}else{return FetchFromCentralCache(index, alignSize);}
}void ThreadCache::Deallocate(void* ptr, size_t size)
{assert(ptr);assert(size <= MAX_BYTES);// 找对映射的自由链表桶,对象插入进入size_t index = SizeClass::Index(size);_freeLists[index].Push(ptr);
}

ConcurrentAlloc.h

#pragma once#include "Common.h"
#include "ThreadCache.h"static void* ConcurrentAlloc(size_t size)
{// 通过TLS 每个线程无锁的获取自己的专属的ThreadCache对象if (pTLSThreadCache == nullptr){pTLSThreadCache = new ThreadCache;}return pTLSThreadCache->Allocate(size);
}static void ConcurrentFree(void* ptr, size_t size)
{assert(pTLSThreadCache);pTLSThreadCache->Deallocate(ptr, size);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/444000.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maven安装,学习笔记,详细整理maven的一些配置

Maven 1. 初识Maven 2. Maven概述 Maven模型介绍 Maven仓库介绍 Maven安装与配置 3. IDEA集成Maven 4. 依赖管理 01. Maven课程介绍 1.1 课程安排 学习完前端Web开发技术后&#xff0c;我们即将开始学习后端Web开发技术。做为一名Java开发工程师&#xff0c;后端 Web开发技术…

Asp.net移除Server, X-Powered-By, 和X-AspNet-Version头

移除X-AspNet-Version很简单,只需要在Web.config中增加这个配置节: <httpRuntime enableVersionHeader"false" />移除Server在Global.asax文件总增加&#xff1a; //隐藏IIS版本 protected void Application_PreSendRequestHeaders() {HttpContext.Current.Res…

RabbitMQ(一):最新版rabbitmq安装

目录 1 简介1.1特性及好处 2 安装2.1 Ubuntu22.04 apt安装最新rabbitmq1、一键部署2、验证3、RabbitMQWeb管理界面及授权操作4、添加远程用户5、一些常用命令 2.2 Docker安装RabbitMQ - Ubuntu22.041、安装docker2、启动rabbitmq 1 简介 RabbitMQ是一个开源的遵循AMQP协议实现…

【大厂AI课学习笔记】1.3 人工智能产业发展(3)

1.3.1 供给侧 技术层面&#xff1a;从实验室走向大规模的商用。 数据层面&#xff1a;数据正式成为重要的生产要素。 市场&#xff1a;供需互促的正向市场环境建立。 资本&#xff1a;走出炒作泡沫&#xff0c;聚焦价值领域。 平台&#xff1a;大厂普遍开放生态。 MORE&am…

js中原型和原型链的特点

文章目录 一、原型二、原型链三、总结参考文献 一、原型 JavaScript 常被描述为一种基于原型的语言——每个对象拥有一个原型对象 当试图访问一个对象的属性时&#xff0c;它不仅仅在该对象上搜寻&#xff0c;还会搜寻该对象的原型&#xff0c;以及该对象的原型的原型&#x…

[C++历练之路]C++多态底层逻辑知多少

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; 前言&#x1f354;&#xff1a;学习了继承与多态&#xff0c;我相信大家对其底层的运用逻辑非常之好奇&#xff0c;今天我们就来探索一下多态中的底层逻辑&#xff0c;话不多说&#xff0c;我们现在开始&#xff01; 目…

BUUCTF-Real-[ThinkPHP]5-Rce

1、ThinkPHP检测工具 https://github.com/anx0ing/thinkphp_scan 漏洞检测 通过漏洞检测&#xff0c;我们发现存在rce漏洞&#xff01; 2、漏洞利用 ---- [!] Name: Thinkphp5 5.0.22/5.1.29 Remote Code Execution VulnerabilityScript: thinkphp5022_5129.pyUrl: http://n…

python打造光斑处理系统4:裁切光斑感兴趣区域

文章目录 图像裁切给定坐标裁切手动阈值裁切 光斑处理&#xff1a;python处理高斯光束的图像 光斑处理系统&#xff1a;程序框架&#x1f31f;打开图像&#x1f31f;参数对话框/伪彩映射 图像裁切 一般来说&#xff0c;光斑只占图像很小一部分&#xff0c;为了更好的观感和更…

剪映使用_

目录 导出的视频大小过大 Adobe Premiere Pro 2022导入视频文件压缩类型不支持 导出的视频大小过大 https://www.bilibili.com/video/BV1mt4y1R75Q Adobe Premiere Pro 2022 教程&#xff1a; https://mp.weixin.qq.com/s/gwYXDCD_iCQTa78tlG6Puw Adobe premiere 安装包下载&…

SpringCloud Gateway(4.1.0) 返回503:原因分析与解决方案

文章目录 一、环境版本二、原因分析三、解决方案 一、环境版本 Versionspring-cloud-dependencies2023.0.0spring-cloud-starter-gateway4.1.0Nacosv2.3.0 二、原因分析 在 Spring Cloud Gateway 的早期版本中&#xff0c;Ribbon 被用作默认的负载均衡器。随着Spring Cloud的…

PHP的线程安全与非线程安全模式选哪个

曾经初学PHP的时候也很困惑对线程安全与非线程安全模式这块环境的选择&#xff0c;也未能理解其中意。近来无意中看到一个教程对线程安全&#xff08;饿汉式&#xff09;&#xff0c;非线程安全&#xff08;懒汉式&#xff09;的描述&#xff0c;虽然觉得现在已经能够很明了透彻…

matplotlib多子图共享坐标轴

文章目录 共用坐标添加共享轴灰度直方图 共用坐标 当一个图像中有多个子图时&#xff0c;若这些子图坐标的含义相同&#xff0c;那么省略一些坐标可以让图像更加简洁。在matplotlib中&#xff0c;通过sharex或者sharey可以起到共享x或y轴坐标的作用。示例如下 import numpy a…