PageCache页缓存

news2025/2/26 14:36:42

一.PageCache基本结构

1.PageCache任务

PageCache负责使用系统调用向系统申请页的内存,给CentralCache分配大块儿的内存,以及合并前后页空闲的内存,整体也是一个单例,需要加锁.

PageCache桶的下标按照页号进行映射,每个桶里span的页数即为下标大小.

2.基本结构

8d4f535f37524e3b95fec5aa7d958676.png

当每个线程的ThreadCache没有内存时都会向central cache申请,此时多个线程的ThreadCache如果访问的不是CentralCache的同一个桶,那么这些线程是可以同时进行访问的。
这时CentralCache的多个桶就可能同时向PageCache申请内存的,所以PageCache也是存在线程安全问题的,因此在访问PageCache时也必须要加锁

  在PageCache这里我们不能使用桶锁,因为当CentralCache向PageCache申请内存时,PageCache可能会将其他桶当中大页的span切小后再给CentralCache。此外,当CentralCache将某个span归还给PageCache时,PageCache也会尝试将该span与其他桶当中的span进行合并
即PageCache内部存在多个桶之间的交互,所以要么所有桶都加锁,要么给PageCache加一把大锁

  也就是说,在访问PageCache时,我们可能需要访问PageCache中的多个桶,如果PageCache用桶锁就会出现大量频繁的加锁和解锁,导致程序的效率低下。因此我们在访问PageCache时使用没有使用桶锁,而是用一个大锁将整个PageCache给锁住。

 此外,page cache在整个进程中也是只能存在一个的,因此我们也需要将其设置为单例模式。

二.class PageCache

20ce72e5f4134d42baafb22747e60f50.png

1283f6470fda4005a313b73a25b1048d.png

三.系统调用接口封装

// 直接去堆上按页申请空间
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等
	size_t bytes = kpage << 13; // kpage 是页数,每页大小为 8 KB(1 << 13 字节)
	ptr = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (ptr == MAP_FAILED) {
		ptr = nullptr; // mmap 失败时返回 MAP_FAILED,我们需要将其转换为 nullptr
	}
#endif

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

	return ptr;
}

inline static void SystemFree(void* ptr)
{
#ifdef _WIN32
	VirtualFree(ptr, 0, MEM_RELEASE);
#else
	// sbrk unmmap等
#endif
}

三.NewSpan获取一个k页的span

// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0);

	// 大于128 page的直接向堆申请
	if (k >= NPAGES)
	{
		void* ptr = SystemAlloc(k);
		//Span* span = new Span;
		Span* span = _spanPool.New();

		span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;

		//_idSpanMap[span->_pageId] = span;
		_idSpanMap.set(span->_pageId, span);

		return span;
	}

	///1._spanLists中有k个page的span
	if (!_spanLists[k].Empty())
	{
		Span* kSpan = _spanLists[k].PopFront();

		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		for (PAGE_ID i = 0; i < kSpan->_n; ++i)
		{
			//_idSpanMap[kSpan->_pageId + i] = kSpan;
			_idSpanMap.set(kSpan->_pageId + i, kSpan);
		}


		return kSpan;
	}

	//2._spanLists[k]为空,从更大的span中寻找
	for (size_t i = k + 1; i < NPAGES; ++i)
	{
		if (!_spanLists[i].Empty())
		{
			Span* nSpan = _spanLists[i].PopFront();
			//Span* kSpan = new Span;
			Span* kSpan = _spanPool.New();

			// 在nSpan的头部切一个k页下来,k页span返回,nSpan再挂到对应映射的位置
			kSpan->_pageId = nSpan->_pageId;
			kSpan->_n = k;

			nSpan->_pageId += k;
			nSpan->_n -= k;

			_spanLists[nSpan->_n].PushFront(nSpan);
			// 存储nSpan的首位页号跟nSpan映射,方便PageCache回收内存时向前向后合并查找
			//_idSpanMap[nSpan->_pageId] = nSpan;
			//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;
			_idSpanMap.set(nSpan->_pageId, nSpan);
			_idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);

			// 建立id和span的映射,方便CentralCache回收小块内存时,查找对应的span
			for (PAGE_ID i = 0; i < kSpan->_n; ++i)
			{
				//_idSpanMap[kSpan->_pageId + i] = kSpan;
				_idSpanMap.set(kSpan->_pageId + i, kSpan);
			}

			return kSpan;
		}
	}

	//3.整个_spanLists都为空,向堆申请一个NPAGES(128)大小的span
	//Span* bigSpan = new Span;
	Span* bigSpan = _spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;

	_spanLists[bigSpan->_n].PushFront(bigSpan);

	//4.递归调用NewSpan重新切分
	return NewSpan(k);
}

f87e23410b324943a7480520097be661.png

 

  1. 因为PageCache是直接按照页数进行映射的,因此我们要从PageCache获取一个k页的span,就应该直接先去找PageCache的第k号桶,如果第k号桶中有span,那我们直接头删一个span返回给CentralCache就行了.
  2. 如果PageCache的第k号桶中没有span,我们就应该继续找后面的桶,只要后面任意一个桶中有一个n页span,我们就可以将其切分成一个k页的span和一个n-k页的span,然后将切出来k页的span返回给CentralCache,再将n-k页的span挂到PageCache的第n-k号桶即可。
  3. 但如果后面的桶中都没有span,此时我们就需要向堆申请一个128页的span了,在向堆申请内存时,直接调用我们封装的SystemAlloc函数即可。  

        需要注意的是,向堆申请内存后得到的是这块内存的起始地址,此时我们需要将该地址转换为页号。由于我们向堆申请内存时都是按页进行申请的,因此我们直接将该地址除以一页的大小即可得到对应的页号。
        递归调用:将申请到的128页的span挂到PageCache对应的哈希桶128号中,然后递归调用该函数,此时在往后找span时就一定会在第128号桶中找到该span,然后进行切分。
这里可以使用递归锁,或者在NewSpan外部加锁,防止递归时产生死锁.

  这里其实有一个问题:当CentralCache向PageCache申请内存时,CentralCache对应的哈希桶是处于加锁的状态的,那在访问PageCache之前我们应不应该把CentralCache对应的桶锁解掉呢?

  这里建议在访问PageCache前,先把CentralCache对应的桶锁解掉。
虽然此时CentralCache的这个桶当中是没有内存供其他ThreadCache申请的,但ThreadCache除了申请内存还会释放内存,如果在访问PageCache前将CentralCache对应的桶锁解掉,那么此时当其他ThreadCache想要归还内存到CentralCache的这个桶时就不会阻塞
因此在调用NewSpan函数之前,我们需要先将CentralCache对应的桶锁解掉,然后再将PageCache的大锁加上,当申请到k页的span后,我们需要将PageCache的大锁解掉,CentralCache拿到k页的span后对其进行切分操作(该过程不需要加锁),在span切好后需要将其挂到CentralCache对应的桶上时,再获取对应的桶锁。

四.MapObjectToSpan哈希

 PageCache在合并span时,是需要通过页号获取到对应的span的,因此要华北库页号与span之间的映射关系,用MapObjectToSpan存储.


Span* PageCache::MapObjectToSpan(void* obj)
{
	PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
	Span* ret = (Span*)_idSpanMap.get(id);
	assert(ret != nullptr);
	return ret;

	//std::unique_lock<std::mutex> lock(_pageMtx);
	//auto ret = _idSpanMap.find(id);
	//if (ret != _idSpanMap.end())
	//{
	//	return ret->second;
	//}
	//else
	//{
	//	assert(false);
	//	return nullptr;
	//}
}

五.ReleaseSpanToPageCache归还Span

void PageCache::ReleaseSpanToPageCache(Span* span)
{
	// 大于128 page的直接还给堆
	if (span->_n > NPAGES - 1)
	{
		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);

		return;
	}

	// 对span前后的页,尝试进行合并,缓解内存碎片问题
	while (1)
	{
		PAGE_ID prevId = span->_pageId - 1;

		//1.前一个page还没有被申请
		auto ret = (Span*)_idSpanMap.get(prevId);
		if (ret == nullptr)break;

		//2.前面相邻页的span在使用,不合并了
		Span* prevSpan = ret;
		if (prevSpan->_isUse == true)break;

		//3.合并出超过128页的span无需合并
		if (prevSpan->_n + span->_n > NPAGES - 1)break;

		span->_pageId = prevSpan->_pageId;
		span->_n += prevSpan->_n;

		_spanLists[prevSpan->_n].Erase(prevSpan);
		//delete prevSpan;
		_spanPool.Delete(prevSpan);
	}

	// 向后合并
	while (1)
	{
		PAGE_ID nextId = span->_pageId + span->_n;

		auto ret = (Span*)_idSpanMap.get(nextId);
		if (ret == nullptr)break;
		Span* nextSpan = ret;
		if (nextSpan->_isUse == true)break;
		if (nextSpan->_n + span->_n > NPAGES - 1)break;

		span->_n += nextSpan->_n;

		_spanLists[nextSpan->_n].Erase(nextSpan);
		//delete nextSpan;
		_spanPool.Delete(nextSpan);
	}

	_spanLists[span->_n].PushFront(span);
	span->_isUse = false;
	//_idSpanMap[span->_pageId] = span;
	//_idSpanMap[span->_pageId+span->_n-1] = span;

	_idSpanMap.set(span->_pageId, span);
	_idSpanMap.set(span->_pageId + span->_n - 1, span);
}

 如果CentralCache中有某个span的_useCount减到0了,CentralCache就需要将这个span还给PageCache.
  这个过程看似是非常简单的,PageCache只需将还回来的span挂到对应的哈希桶上就行了。但实际为了缓解内存碎片的问题,PageCache还需要尝试将还回来的span与其他空闲的span进行合并

 合并的过程可以分为向前合并和向后合并.
如果还回来的span的起始页号是num,该span所管理的页数是n.
那么在向前合并时,就需要判断第num-1页对应span是否空闲,如果空闲则可以将其进行合并,并且合并后还需要继续向前尝试进行合并,直到不能进行合并为止.
而在向后合并时,就需要判断第num+n页对应的span是否空闲,如果空闲则可以将其进行合并,并且合并后还需要继续向后尝试进行合并,直到不能进行合并为止.

  因此PageCache在合并span时,是需要通过页号获取到对应的span的,这就是我们要把页号与span之间的映射关系存储到PageCache的原因.

  但需要注意的是,当我们通过页号找到其对应的span时,这个span此时可能挂在PageCache,也可能挂在CentralCache。而在合并时我们只能合并挂在PageCache的span,因为挂在CentralCache的span当中的对象正在被其他线程使用。

  可是我们不能通过span结构当中的_useCount成员,来判断某个span到底是在CentralCache还是在PageCache.因为当CentralCache刚向PageCache申请到一个span时,这个span的_useCount就是等于0的,这时可能当我们正在对该span进行切分的时候,PageCache就把这个span拿去进行合并了,这显然是不合理的。  
        因此,我们可以在span结构中再增加一个_isUse成员,用于标记这个span是否正在被使用,而当一个span结构被创建时我们默认该span是没有被使用的。
        即在PageCache中,_isUse=false,在CentralCache中为true

 

由于在合并PageCache当中的span时,需要通过页号找到其对应的span,而一个span是在被分配给CentralCache时,才建立的各个页号与span之间的映射关系,因此PageCache当中的span也需要建立页号与span之间的映射关系。

  与CentralCache中的span不同的是,在PageCache中,只需建立一个span的首尾页号与该span之间的映射关系。因为当一个span在尝试进行合并时,如果是往前合并,那么只需要通过一个span的尾页找到这个span,如果是向后合并,那么只需要通过一个span的首页找到这个span。也就是说,在进行合并时我们只需要用到span与其首尾页之间的映射关系就够了。  
        因此当我们申请k页的span时,如果是将n页的span切成了一个k页的span和一个n-k页的span,我们除了需要建立k页span中每个页与该span之间的映射关系之外,还需要建立剩下的n-k页的span与其首尾页之间的映射关系。

PageCache.cpp/.h

PageCache.cpp

#include "PageCache.h"

PageCache PageCache::_sInst;
std::mutex PageCache::_pageMtx;

// 获取一个K页的span
Span* PageCache::NewSpan(size_t k)
{
	assert(k > 0);

	// 大于128 page的直接向堆申请
	if (k >= NPAGES)
	{
		void* ptr = SystemAlloc(k);
		//Span* span = new Span;
		Span* span = _spanPool.New();

		span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
		span->_n = k;

		//_idSpanMap[span->_pageId] = span;
		_idSpanMap.set(span->_pageId, span);

		return span;
	}

	///1._spanLists中有k个page的span
	if (!_spanLists[k].Empty())
	{
		Span* kSpan = _spanLists[k].PopFront();

		// 建立id和span的映射,方便central cache回收小块内存时,查找对应的span
		for (PAGE_ID i = 0; i < kSpan->_n; ++i)
		{
			//_idSpanMap[kSpan->_pageId + i] = kSpan;
			_idSpanMap.set(kSpan->_pageId + i, kSpan);
		}


		return kSpan;
	}

	//2._spanLists[k]为空,从更大的span中寻找
	for (size_t i = k + 1; i < NPAGES; ++i)
	{
		if (!_spanLists[i].Empty())
		{
			Span* nSpan = _spanLists[i].PopFront();
			//Span* kSpan = new Span;
			Span* kSpan = _spanPool.New();

			// 在nSpan的头部切一个k页下来,k页span返回,nSpan再挂到对应映射的位置
			kSpan->_pageId = nSpan->_pageId;
			kSpan->_n = k;

			nSpan->_pageId += k;
			nSpan->_n -= k;

			_spanLists[nSpan->_n].PushFront(nSpan);
			// 存储nSpan的首位页号跟nSpan映射,方便PageCache回收内存时向前向后合并查找
			//_idSpanMap[nSpan->_pageId] = nSpan;
			//_idSpanMap[nSpan->_pageId + nSpan->_n - 1] = nSpan;
			_idSpanMap.set(nSpan->_pageId, nSpan);
			_idSpanMap.set(nSpan->_pageId + nSpan->_n - 1, nSpan);

			// 建立id和span的映射,方便CentralCache回收小块内存时,查找对应的span
			for (PAGE_ID i = 0; i < kSpan->_n; ++i)
			{
				//_idSpanMap[kSpan->_pageId + i] = kSpan;
				_idSpanMap.set(kSpan->_pageId + i, kSpan);
			}

			return kSpan;
		}
	}

	//3.整个_spanLists都为空,向堆申请一个NPAGES(128)大小的span
	//Span* bigSpan = new Span;
	Span* bigSpan = _spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	bigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	bigSpan->_n = NPAGES - 1;

	_spanLists[bigSpan->_n].PushFront(bigSpan);

	//4.递归调用NewSpan重新切分
	return NewSpan(k);
}

Span* PageCache::MapObjectToSpan(void* obj)
{
	PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
	Span* ret = (Span*)_idSpanMap.get(id);
	assert(ret != nullptr);
	return ret;

	//std::unique_lock<std::mutex> lock(_pageMtx);
	//auto ret = _idSpanMap.find(id);
	//if (ret != _idSpanMap.end())
	//{
	//	return ret->second;
	//}
	//else
	//{
	//	assert(false);
	//	return nullptr;
	//}
}


void PageCache::ReleaseSpanToPageCache(Span* span)
{
	// 大于128 page的直接还给堆
	if (span->_n > NPAGES - 1)
	{
		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);

		return;
	}

	// 对span前后的页,尝试进行合并,缓解内存碎片问题
	while (1)
	{
		PAGE_ID prevId = span->_pageId - 1;

		//1.前一个page还没有被申请
		auto ret = (Span*)_idSpanMap.get(prevId);
		if (ret == nullptr)break;

		//2.前面相邻页的span在使用,不合并了
		Span* prevSpan = ret;
		if (prevSpan->_isUse == true)break;

		//3.合并出超过128页的span无需合并
		if (prevSpan->_n + span->_n > NPAGES - 1)break;

		span->_pageId = prevSpan->_pageId;
		span->_n += prevSpan->_n;

		_spanLists[prevSpan->_n].Erase(prevSpan);
		//delete prevSpan;
		_spanPool.Delete(prevSpan);
	}

	// 向后合并
	while (1)
	{
		PAGE_ID nextId = span->_pageId + span->_n;

		auto ret = (Span*)_idSpanMap.get(nextId);
		if (ret == nullptr)break;
		Span* nextSpan = ret;
		if (nextSpan->_isUse == true)break;
		if (nextSpan->_n + span->_n > NPAGES - 1)break;

		span->_n += nextSpan->_n;

		_spanLists[nextSpan->_n].Erase(nextSpan);
		//delete nextSpan;
		_spanPool.Delete(nextSpan);
	}

	_spanLists[span->_n].PushFront(span);
	span->_isUse = false;
	//_idSpanMap[span->_pageId] = span;
	//_idSpanMap[span->_pageId+span->_n-1] = span;

	_idSpanMap.set(span->_pageId, span);
	_idSpanMap.set(span->_pageId + span->_n - 1, span);
}

PageCache.h

#pragma once

#include "Common.h"
#include "ObjectPool.h"
#include "PageMap.h"

class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_sInst;
	}

	// 获取从内存对象到span的映射
	Span* MapObjectToSpan(void* obj);
	// 释放空闲span回到Pagecache,并合并相邻的span
	void ReleaseSpanToPageCache(Span* span);
	// 获取一个K页的span
	Span* NewSpan(size_t k);

	static std::mutex _pageMtx;
private:
	SpanList _spanLists[NPAGES];
	ObjectPool<Span> _spanPool;

	//std::unordered_map<PAGE_ID, Span*> _idSpanMap;
#ifdef _WIN64
	TCMalloc_PageMap3<64 - PAGE_SHIFT> _idSpanMap;
#else
	TCMalloc_PageMap1<32 - PAGE_SHIFT> _idSpanMap;
#endif

private:
	PageCache(){}

	PageCache(const PageCache&) = delete;


	static PageCache _sInst;
};

 

 

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

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

相关文章

【话题】AI是在帮助开发者还是取代他们

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读小5的系列文章&#xff0c;这是《话题》系列文章 目录 引言AI在代码生成中的应用AI在错误检测和自动化测试中的作用对开发者职业前景的影响技能需求的变化与适应策略结论文章推荐 引言 随着人工智能&#xff08;AI&#xff…

Python 获取tiktok视频评论回复数据 api接口

TIKTOK api接口 用于爬取tiktok视频评论回复数据 详细采集页面如图 https://www.tiktok.com/dailymail/video/7329872821990182190?qneural%20link&t1706783508149 请求API http://api.xxxx.com/tt/video/info/comment/reply?video_id7288909913185701125&comment_…

YOLOv8改进 | 注意力机制 | 结合静态和动态上下文信息的注意力机制

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效…

【Springer出版 | EI稳定检索】第五届物联网、人工智能与机械自动化国际学术会议 (IoTAIMA 2024,7月19-21)

由浙江工业大学主办&#xff0c;第五届物联网、人工智能与机械自动化国际学术会议 (IoTAIMA 2024) 将于2024年7月19-21日在浙江杭州召开。 会议旨在为从事物联网、人工智能与机械自动化的专家学者、工程技术人员、技术研发人员提供一个共享科研成果和前沿技术&#xff0c;了解学…

ubuntu22 sshd设置

专栏总目录 一、安装sshd服务 sudo apt updatesudo apt install -y openssh-server 二、配置sshd 使用文本编辑器打开/etc/ssh/sshd_config sudo vi /etc/ssh/sshd_config &#xff08;一&#xff09;配置sshd服务的侦听端口 建议将ssh的侦听端口改为7000以上的端口&#…

大数的排列组合公式C代码

我们知道&#xff0c;计算排列A(n,m)和组合C(n,m)可以用先求阶乘的方式实现&#xff0c;但是当数很大时求阶乘很容易溢出&#xff0c;所以这里给出非阶乘的方式求排列组合 求排列 分母和分子可以抵消&#xff0c;最后代码如下 unsigned long long A(int n, int m) {unsigned…

Buuctf之SimpleRev做法

首先&#xff0c;查个壳&#xff0c;64bit&#xff0c;那就丢进ida64中进行反编译进来之后&#xff0c;我们进入main函数&#xff0c;发现里面没什么东西&#xff0c;那就shiftf12搜索字符串&#xff0c;找到关键字符串&#xff0c;双击进入然后再选中该字符串&#xff0c;ctrl…

Android仿天眼查人物关系图

效果图预览 绘制思路 这里使用了中学解析几何知识 XPoint OPointX OPointXcosθ&#xff1b; YPoint OPointY OPointYsinθ&#xff1b; canvas.drawText(lists.get(i).getName(), XPoint (float) Math.cos(pere * i 5) * radius[i % radius.length] - 30, YPoint (fl…

TaskDisplayArea、ActivityStack、Task以及ActivityRecord之间的关系

目录 一、TaskDisplayArea、ActivityStack、TaskRecord以及ActivityRecord关系图 二、TaskDisplayArea、ActivityStack、Task以及ActivityRecord相关代码 1、ActivityRecord 2、Task 3、ActivityStack 4、TaskDisplayArea 前言&#xff1a;对TaskDisplayArea、ActivitySt…

【图解大数据技术】Flume、Kafka、Sqoop

【图解大数据技术】Flume、Kafka、Sqoop FlumeFlume简介Flume的应用场景 KafkaKafka简介Kafka架构Flume与Kafka集成 SqoopSqoop简介Sqoop原理sqoop搭配任务调度器实现定时数据同步 Flume Flume简介 Flume是一个数据采集工具&#xff0c;多用于大数据技术架构下的日志采集。 …

论文略读:Learning and Forgetting Unsafe Examples in Large Language Models

随着发布给公众的大语言模型&#xff08;LLMs&#xff09;数量的增加&#xff0c;迫切需要了解这些模型从第三方定制的微调数据中学习的安全性影响。论文研究了在包含不安全内容的噪声定制数据上微调的LLMs的行为&#xff0c;这些数据集包含偏见、毒性和有害性 发现虽然对齐的L…

尚庭公寓——数据库设计

1. 数据的关系 一对一&#xff0c;一对多&#xff08;多对一&#xff09;&#xff0c;多对多 2. 实体关系模型 实体关系模型常用ER图&#xff08;enity relationship graph&#xff09;表示&#xff1b; 矩形表示实体&#xff08;类似Java中的对象&#xff0c;如学生就是一…

Micron近期发布了32Gb DDR5 DRAM

Micron Technology近期发布了一项内存技术的重大突破——一款32Gb DDR5 DRAM芯片&#xff0c;这项创新不仅将存储容量翻倍&#xff0c;还显著提升了针对人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;、高性能计算&#xff08;HPC&#xff09;以及数…

进程的控制-孤儿进程和僵尸进程

孤儿进程 &#xff1a; 一个父进程退出&#xff0c;而它的一个或多个子进程还在运行&#xff0c;那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程( 进程号为 1) 所收养&#xff0c;并由 init 进程对它们完成状态收集工作 为了释放子进程的占用的系统资源&#xff1a; …

每日一题~ (判断是否是合法的出栈序列)

大概的题意&#xff1a; 将 1-n 按照顺序进栈&#xff0c;问 输入的序列是否是合法的出栈序列。 遍历序列&#xff0c;如果当前这个值a小于 栈顶的值&#xff0c;说明它还未进栈&#xff08;因为我们是按照顺序进栈的&#xff09;&#xff0c;所以我们将 一些元素进栈&#xff…

SpringMVC:SpringMVC执行流程

文章目录 一、介绍二、什么是MVC 一、介绍 Spring MVC 是一种基于Java的Web框架&#xff0c;它采用了MVC&#xff08;Model - View - Controller&#xff09;设计模式&#xff0c;通过吧Model、View和Controller分离&#xff0c;将Web层进行职责解耦&#xff0c;把复杂的Web应…

【C++】解决 C++ 语言报错:Invalid Array Index

文章目录 引言 无效数组索引&#xff08;Invalid Array Index&#xff09;是 C 编程中常见且危险的错误之一。当程序试图使用不合法的索引访问数组时&#xff0c;就会发生无效数组索引错误。这种错误不仅会导致程序崩溃&#xff0c;还可能引发不可预测的行为和安全漏洞。本文将…

MySQL基础篇(二)字符集以及校验规则

在MySQL基础篇&#xff08;一&#xff09;中&#xff0c;我们知道了如何创建数据库&#xff0c;这篇文章带大家了解创建的一些细节。 红色框&#xff1a;可省略&#xff0c;作用如果存在相同的数据库名称&#xff0c;就不会再创建&#xff0c;反之&#xff0c;创建。 蓝色框&…

数据库考试大纲重点总结

考试大纲 第一章 5-10分 10分第二章 5-10分 10分1、计算 除/象集2、连接3、选择4、投影5、代数表达式综合 第三章 20分 20分1、修改表的信息用UPDATE2、创建索引3、有效性检查CHECK 和 缺省值4、数据库表修改SQL语句5、挑选表中的数据赋给中间变量&#xff08;一般用于存储过程…

PyCharm如何安装requirements.txt中的依赖包

问题&#xff1a;下载别人的源码&#xff0c;如何安装代码中requirement.txt中的依赖包。 解决方案&#xff1a; &#xff08;1&#xff09;打开PyCharm下面的Terminal&#xff0c;先为代码创建单独的虚拟环境并进入到虚拟环境中&#xff08;每个项目单独的环境&#xff0c;这…