【项目日记】高并发内存池---实现页缓存

news2024/11/27 16:31:12

在这里插入图片描述

放纵自己的欲望是最大的祸害;
谈论别人的隐私是最大的罪恶;
不知自己过失是最大的病痛。
--- 亚里士多德 ---

高并发内存池---实现页缓存

  • 1 页缓存整体设计思路
  • 2 框架搭建
  • 3 NewSpan函数
  • 4 请求Span联动

1 页缓存整体设计思路

首先我们来看页缓存的设计思路,明白思路,代码就可以更加舒畅的写出来,并且这个项目的调试比较困难,一定一定要仔细明白设计思路,把代码仔细写好才能保证我们的开发效率!

页缓存是所有线程共享的,当线程缓存内部的_freelist中没有内存块时,会向中心缓存进行申请。中心缓存内部的_spanlist如果span中还有内存块就可以进行返回,如果没有就要去页缓存进行Span的申请。页缓存全局只有一个!

  • 页缓存需要设计为单例模式!这里使用懒汉模式实现!

页缓存内部与线程缓存和中心缓存不一样,内部不是通过内存大小进行的映射,而是通过页的数量来进行映射。每次中心缓存申请是时候会标明申请的页数,进而找到对应页的链表,查看是否有可以使用的span,如果没有,就向页数大的链表查找,如果有就进行拆分!

  • 页缓存内部存在以页数作为映射的链表数组!
  • 页缓存内部的申请机制是先在对应页数的链表中进行查找,命中就直接进行返回。没有有命中就向页数大的链表中进行查找,命中就进行拆分。如果没有命中,就创建一个最大页数的span,挂载到对应链表上!

2 框架搭建

现在我们来通过单例模式搭建其基础的框架:

  1. 类内设置唯一静态对象,注意要在类外进行定义!
  2. 设置GetInstance(),可以获取唯一静态对象,在第一次获取时进行创建!
  3. 注意需要单例锁来进行保护,因为第一次获取时很有可能会让两个线程同时进来,就会导致错误!
class PageCache
{
public:
	//懒汉模式进行时间
	static PageCache* GetInstance()
	{
		if (_pinfo == nullptr)
		{
			std::unique_lock<std::mutex> lock(_single_mtx);
			if (_pinfo == nullptr)
			{
				_pinfo = new PageCache();
			}
		}
		return _pinfo;
	}
	//给中心缓存提供span的接口
	Span* NewSpan(size_t num);
private:
	//根据span的页的个数进行分类
	SpanList _pageList[PAGENUM]; // 1 - 128
	//页锁
	std::mutex _page_mtx;
//单例模式设计
private:
	static PageCache* _pinfo;		//需要类外声明
	static std::mutex _single_mtx;	//单例锁
	//构造函数私有化
	PageCache() {};
	//拷贝构造 赋值重载 删除
	PageCache(const PageCache&) = delete;
	PageCache operator=(const PageCache&) = delete;

};

3 NewSpan函数

NewSpan函数通过中心缓存提供的页数来进行查找:

  1. 首先先在num对应的链表中查找是否有可以使用的span。
  2. 如果没有就向页数大的链表中进行查找,如果找到就进行拆分,分成两个span,一个用来返回,另一个再重新插入到对应链表中
  3. 如果没有就创建一个大span,挂载到最大页数对应的链表中,然后再次返回调用NewSpan进行递归即可!
Span* PageCache::NewSpan(size_t num)
{
	// 1 - 128
	assert(num > 0 && num < PAGENUM);
	//先在当前页对应的链表中进行寻找,没有就向后寻找
	if (!_pageList[num].Empty())
	{
		//不为空就直接返回一个span对象
		return _pageList->PopFront();
	}
	//如果没有 遍历后续的链表 查看有没有更大的span
	else
	{
		for (int i = num + 1; i < PAGENUM; i++)
		{
			if (!_pageList[i].Empty())
			{
				//不为空 说明存在页数为 i 的 span
				//进行拆分
				Span* kspan = new Span;
				Span* nspan = _pageList[i].PopFront();

				//kspan 的赋值
				kspan->_n = num;
				kspan->_pageid = nspan->_pageid;

				nspan->_n -= num;
				nspan->_pageid += num;

				_pageList[nspan->_n].PushFront(nspan);
				return kspan;
			}
		}
	}
	//到了这里就说明整个_pagelist中没有可以使用的span , 需要申请一个大span
	Span* bigspan = new Span;
	//申请大空间
	void* ptr = SystemAlloc(PAGENUM - 1);
	//通过ptr初始化newspan
	bigspan->_n = PAGENUM - 1;
	bigspan->_pageid = (PAGE_ID)ptr >> PAGE_SHIFT;
	
	//插入到_pagelist中
	_pageList[PAGENUM - 1].PushFront(bigspan);

	//递归调用
	return NewSpan(num);
}

注意NewSpan函数是对临界区的操作,需要进行加锁,但是这里设计到了递归调用函数,在函数内使用普通的互斥锁会造成死锁,对于这个问题一般有三种解决方案:

  1. 使用递归锁recursive_mutex,就可以避免出现递归造成的死锁问题!
  2. 设置子函数,我们函数NewSpan中进行上锁,然后调用子函数_NewSpan进行操作!
  3. 在进入NewSpan之前就上好锁,结束退出之后再解锁,类似子函数的解决方案!

这里还有加入对应的内存开辟函数,使用条件编译,使其适配所有的操作系统!

#ifdef _WIN32
#define NOMINMAX//避免和std中的min max冲突!
#include<windows.h>
#else
// linux
#endif


// --- 直接去堆上按页申请空间 ---
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
	void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
	// linux下brk mmap等
#endif

	if (ptr == nullptr)
		throw std::bad_alloc();

	return ptr;
}

4 请求Span联动

我们回到中心缓存的GetOneSpan 函数中,线程缓存向这里索要span,中心缓存给不出就去页缓存申请Span:

  1. 首先先在中心缓存中的spanlist中进行查找,如果有就直接进行返回
  2. 没有span就去页缓存中进行申请,注意这里要把list桶锁解掉,因为操作会去到页缓存,离开list临界区。如果继续上锁会影响释放内存的操作!
  3. 从页缓存中获取到span就进行处理:
    • 首先通过span_pageid确定地址区间,然后通过startend两个指针将span内部的大内存块拆分成小内存块尾插到span->_freelist中。尾插物理空间连续, 缓存利用率高!
    • 最后将span插入到链表中,并将其返回!线程缓存会对span_freelist中的内存块进行操作!
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	//...等待联动...
	//向spanlist中寻找是否有span可以使用
	Span* it = list.Begin();
	//找到挂载着内存块的span!
	if (it != list.End())
	{
		if (it->_freelist != nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;
		}
	}
	//到这里说明spanlist中没有span对象
	//没有就向页缓存申请
	//1. 需要计算需要申请几个页! SizeClass::NumMovePage
	size_t num = SizeClass::NumMovePage(size);
	list.GetMutex().unlock();//将桶锁解开  避免影响该链表是内存的释放
	//根据页数申请span
	PageCache::GetInstance()->GetMuetx().lock(); // 上页锁
	Span* span = PageCache::GetInstance()->NewSpan(num);
	PageCache::GetInstance()->GetMuetx().unlock();
	//不需要立刻恢复桶锁 到临界区在上锁就可以

	//获取到span之后进行处理
	//通过获取的span内部的属性确定其映射的地址区间!
	//通过span的页号确定span的起始地址
	char* start = (char*)(span->_pageid << PAGE_SHIFT);
	char* end = start + (num << PAGE_SHIFT);//页数确定内存字节数大小

	//把大块内存切好挂接起来 --- 最好使用尾插 物理空间连续 缓存利用率高
	span->_freelist = start;
	start += size;
	void* tail = span->_freelist;
	while (start < end)
	{
		NextObj(tail) = start;
		tail = start; //tail = NextObj(tail) 
		start += size;
	}
	//放入spanlist中 需要加锁
	list.GetMutex().lock();
	list.PushFront(span);

	return span;

	//return nullptr;
}

这样就形成闭环,我们的申请大致的框架就已经完了,下一篇文章我们来进行申请联调的测试!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows手工杀毒-寻找可疑进程之进程名称

上篇回顾&#xff1a;windows手工杀毒-寻找可疑进程之进程图标-CSDN博客 上篇中我们简单介绍了什么是电脑病毒&#xff0c;也介绍了一种发现可疑进程的方法即根据进程图标确认是否是病毒&#xff0c;这种方法存在的理论基础是&#xff0c;通过图标可以很容易在电脑上找…

遥控器新手操作指南!!!

一、准备工作 检查电量&#xff1a;确保无人机和遥控器的电池电量充足&#xff0c;以避免在飞行过程中因电量不足而导致意外。 安装与连接&#xff1a;确保无人机的螺旋桨安装正确且牢固&#xff0c;同时检查无人机存储卡是否插入&#xff0c;以及遥控器与无人机之间的连接是…

论文笔记:2023顶会SIGIR - Strategy-aware Bundle Recommender System

论文笔记&#xff1a;2023顶会SIGIR - Strategy-aware Bundle Recommender System

【位运算】--- 初阶题目赏析

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 算法Journey 根据上一篇位运算的总结&#xff0c;我们来体会几道初阶题目。 &#x1f3e0; 判定字符是否唯一 &#x1f4cc; 题目解析 判定字符是否唯一…

通义千问AI PPT初体验:一句话、万字文档、长文本一键生成PPT!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;专注于分享AI全维度知识&#xff0c;包括但不限于AI科普&#xff0c;AI工…

leecode刷题经典算法套路模版笔记【递归回溯篇】--根本逻辑,快速掌控

刷题套路总结&#xff1a; 双指针&#xff1a; 单调性&#xff1b;对两端按照规律进行操作移动&#xff1b; 常见移法&#xff0c;右指针右移扩大范围&#xff0c;左指针左移缩小范围&#xff1b; 先举例模拟&#xff0c;然后推导公式&#xff1b; 递归&#xff0c;回溯 &am…

线程间同步的方式有哪些?

Linux 系统提供了五种用于线程间同步的方式&#xff1a;互斥锁、读写锁、自旋锁、信号量、条件变量 互斥锁 主要用于保护共享数据&#xff0c;确保同一时间内只有一个线程访问数据。 互斥量本质上来说就是一把锁&#xff0c;在访问共享资源前对互斥量进行加锁&#xff0c;访…

【go-zero】win启动rpc服务报错 panic: context deadline exceeded

win启动rpc服务报错 panic: context deadline exceeded 问题来源 在使用go-zero生成的rpc项目后 启动不起来 原因 这个问题原因是wndows没有启动etcd 官方文档是删除了etcd配置 而我自己的测试yaml配置有etcd&#xff0c;所以需要启动etcd 下载安装好etcd后&#xff0…

Java Full GC 的常见原因及优化策略

Java Full GC 的常见原因及优化策略 1、导致Full GC的常见原因1.1 新生代设置过小1.2 新生代设置过大1.3 Survivor区设置不当 2、优化GC策略2.1 吞吐量优先2.2 暂停时间优先 3、结论 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java应…

自控原理-传递函数(闭环 扰动 偏差 前馈 复合 顺馈)

都知道闭环传递函数定义为&#xff1a;G1G2/(1G1G2H) 但是当碰到复杂的系统&#xff0c;比如复合顺馈&#xff0c;前馈扰动等&#xff0c;就不知道分子到底要不要乘上G2了。 这个公式是如何推导出来的&#xff0c;今天看到一个公式图片&#xff1a; 过程非常详细。 由此我也…

C语言遇见的一些小问题

问题如下&#xff1a; 1&#xff1a;为什么这样的代码为报错 #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <algorithm> #include <cstdio> #include<string> #include<stdlib.h> using namespace std; int main() {int i …

C语言 ——— #define定义标识符

目录 #define 定义常变量 #define 定义字符串 #define 定义一条代码 #define 定义的标识符是否需要加分号 #define 定义常变量 代码演示&#xff1a; #define M 100 //定义常变量 代码用途&#xff1a; int a M; int arr[M] { 0 }; 此时的 M 具有常属性&#xff0c…

什么是UART?

1.什么是UART&#xff1f; 通用异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter)&#xff0c;通常称作UART。UART 表示通用异步接收机/发射机&#xff0c;定义用于在两个设备之间交换串行数据的协议或一组规则。UART 非常简单&#xff0c;仅在发射机和…

C语言中static与extern关键字的深入解析

在C语言编程中&#xff0c;static和extern是两个非常重要的关键字&#xff0c;它们各自有着独特的用途。本文将深入探讨这两个关键字的工作原理、底层实现机制以及在实际开发中的应用。 static关键字 1. 原理与作用 static关键字用于声明变量或函数具有特定的作用域和生命周…

5.4分段线性灰度变换

目录 实验原理 分段线性灰度变换的概念 变换函数的形式 示例代码1 示例结果1 示例代码2 示例结果2 示例代码3 运行结果3 示例代码4 运行结果4 实验原理 在OpenCV中&#xff0c;分段线性灰度变换&#xff08;Piecewise Linear Gray Level Transformation&#xff09…

GitLab 是什么?GitLab使用常见问题解答

GitLab 是什么 GitLab是由GitLab Inc.开发&#xff0c;使用MIT许可证的基于网络的Git仓库管理工具开源项目&#xff0c;且具有wiki和issue跟踪功能&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的web服务。 ​GitLab 是由 GitLab Inc.开发&#xff0c…

【Prometheus】Prometheus的特点、数据采集方式、架构、数据模型详解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

vue3整合antv x6实现图编辑器快速入门

安装&#xff1a; npm install antv/x6 --save如果使用 umd 包&#xff0c;可以使用下面三个 CDN 中的任何一个&#xff0c;默认使用 X6 的最新版&#xff1a; https://unpkg.com/antv/x6/dist/index.jshttps://cdn.jsdelivr.net/npm/antv/x6/dist/index.jshttps://cdnjs.clo…

从汇编层看64位程序运行——likely提示编译器的优化案例和底层实现分析

大纲 代码分析with_attributes::powno_attributes::pow分析 我们在《Modern C——使用分支预测优化代码性能》一文中介绍了likely提示编译器进行编译优化&#xff0c;但是我们又讲了最终优化不是对分支顺序的调换&#xff0c;那么它到底做了什么样的优化&#xff0c;让整体性能…

个人旅游网(5)——功能详解——购物车功能

文章目录 一、设计购物车二、购物车对redis的一系列操作三、购物车3.1、接口详解3.1.1、addCart&#xff08;将当前旅游路线加入到购物车中&#xff09;3.1.2、showCartItem&#xff08;显示刚刚加入购物车的商品&#xff09;3.1.3、findAll&#xff08;将购物车里的所有旅游路…