007高并发内存池_回收内存

news2024/11/23 15:40:00

​🌈个人主页:Fan_558
🔥 系列专栏:高并发内存池
🌹关注我💪🏻带你学更多知识

在这里插入图片描述

文章目录

  • 前言
    • 一、ThreadCache回收内存
    • 二、CentralCache回收内存
      • 2.1 建立映射
    • 三、PageCache回收内存
  • 小结

前言

本文将会向你介绍ThreadCache、CentralCache、PageCache是如何回收内存的
在这里插入图片描述

一、ThreadCache回收内存

1、当某个线程肾申请的内存对象不再使用了,就会释放到threadcache中,然后threadcache将内存对象插入到对应的哈希桶的自由链表中
2、线程不断地释放内存对象,最终在某个哈希桶中的自由链表的长度会越来越长,每一个线程都有一个独立的threadcache,若是不及时返还给centralcache,会造成浪费
3、当线程二向centralcache申请的时候,若是线程一占有太多空间资源了,导致线程二向centralcache申请,然后centralcache还需要向下一层pagecache进行申请
4、因此当一个线程中在threadcache所闲置的内存对象过多,我们应该将释放到的内存对象返还给central cache

//释放pTLSThreadCache对象(需要告诉释放哪一个桶)
《ThreadCache.cpp》
void ThreadCache::Deallocate(void* ptr, size_t size)
{
	assert(ptr);
	assert(size < MAX_BYTES);
	//计算出属于哪一个桶
	size_t index = AlignmentRules::Index(size);
	//用自由链表管理释放的内存
	_freeLists[index].Push(ptr);
	//当链表长度大于一次批量申请的内存时就开始还一段list给central cache
	if (_freeLists[index].Size() >= _freeLists[index].MaxSize())
	{
		ListTooLong(_freeLists[index], size);
	}
}

当自由链表的长度大于或等于threadcache一次批量向centralcache申请的内存对象个数,此时就需要把该自由链表中的对象返还给centralcache中对应的span

《ThreadCache.cpp》
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
	void* start = nullptr;
	void* end = nullptr;
	//从自由链表中取出批量个对象
	list.PopRange(start, end, list.MaxSize());
	//将批量的内存对象返还到对应的span中
	CentralCache::GetInstance()->ReleaseListToSpans(start, size);
}

基于以上需求,我们应该对自由链表FreeList类进行修改,新增一个Size函数用来获取自由链表中的个数,新增一个PopRange函数用于从自由链表中取出指定个数的内存对象,并且新增一个_size变量记录自由链表中内存对象的个数,任何有对自由链表内存对象个数修改的地方都应该更新_size变量

《Common.h》
//管理切分好的小对象的自由链表
class FreeList
{
public:
	//将释放的对象头插到自由链表
	void Push(void* obj)
	{
		assert(obj);

		//头插
		NextObj(obj) = _freeList;
		_freeList = obj;
		_size++;
	}
	//从自由链表头部获取一个对象
	void* Pop()
	{
		assert(_freeList);

		//头删
		void* obj = _freeList;
		_freeList = NextObj(_freeList);
		_size--;

		return obj;
	}
	//插入一段范围的对象到自由链表
	void PushRange(void* start, void* end, size_t n)
	{
		assert(start);
		assert(end);

		//头插
		NextObj(end) = _freeList;
		_freeList = start;
		_size += n;
	}
	//从自由链表获取一段范围的对象
	void PopRange(void*& start, void*& end, size_t n)
	{
		assert(n <= _size);

		//头删
		start = _freeList;
		end = start;
		for (size_t i = 0; i < n - 1;i++)
		{
			end = NextObj(end);
		}
		_freeList = NextObj(end); //自由链表指向end的下一个对象
		NextObj(end) = nullptr; //取出的一段链表的表尾置空
		_size -= n;
	}
	bool Empty()
	{
		return _freeList == nullptr;
	}
	size_t& MaxSize()
	{
		return _maxSize;
	}
	size_t Size()
	{
		return _size;
	}
private:
	void* _freeList = nullptr; //自由链表
	size_t _maxSize = 1;
	size_t _size = 0;
};

二、CentralCache回收内存

1、当threadcache将自由链表中的内存对象返还给centralcache的span的时候会存在一个问题,这些对象应该返还给哪一个span,在centralcache中每个哈希桶当中都可能不止一个span,因此我们需要知道每个对象对应的是哪一个span
2、我们知道一个span可以切分为多个小内存对象,对内存对象的地址➗一页的大小(假设为8K),可以得到该内存对象所在的页号,一个span结构包含页号、页数等等成员变量,由此我们可以建立页号与span的对应关系,这样,已知内存对象的地址就可以知道页号,进一步就可以得知该内存对象是属于哪一个span了

比如从2000页到2001页之间的任意地址(一个个小内存对象的地址)➗8K都是2000,因为其它地方不够整除就舍弃了

《UnitTest.cpp》
void TestAddressShift()
{
	PAGE_ID id1 = 2000;      //页号1
	PAGE_ID id2 = 2001;     //页号2
	char* p1 = (char*)(id1 << PAGE_SHIFT);   //获取内存块的起始地址
	char* p2 = (char*)(id2 << PAGE_SHIFT);
	while (p1 < p2)
	{
		cout << (void*)p1 << ":" << ((PAGE_ID)p1 >> PAGE_SHIFT) << endl;   //获取每次地址对应对应的页号
		p1 += 8;  
	}
}

在这里插入图片描述

在这里插入图片描述

2.1 建立映射

知道一个内存块的地址,就能知道页号,建一个map<页号与span指针的映射>就能找到span

《PageCache.h》
class PageCache
{
public:
	static PageCache* GetInstance()
	{
		return &_sInst;
	}

	//获取一个K页的span
	Span* NewSpan(size_t K);

	//获取从对象到span的映射
	Span* MapObjectToSpan(void* obj);

	//释放空闲span回到pagecache,并合并相邻的span
	void ReleaseSpanToPageCache(Span* span);

	std::mutex _pageMutex;
private:
	SpanList _spanLists[NPAGES];
	std::unordered_map<PAGE_ID, Span*> _idSpanMap;
private:
	PageCache()
	{}

	
	//防拷贝
	PageCache(const PageCache&) = delete;
	static PageCache _sInst;
};

最初span从哪里来呢?当最初申请内存对象的时候,会一层层往下找,直到找到pagecache,pagecache从堆中申请出一个128页的span,并按照申请的需求切分给centralcache,由此我们应该从pagecache申请出来span的时候建立映射

《PageCache.cpp》
//在pagecache中获取一个n页的span
Span* PageCache::NewSpan(size_t K)
{
	assert(K > 0);
	//大于128页的直接向对进行申请 
	if (K > NPAGES - 1)
	{
		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;	//建立映射关系
		return span;
	}

	//std::cout << K << std::endl;
	assert(K > 0 && K < NPAGES);
	//检查pagecache第K个桶是否有span
	if (!_spanLists[K].Empty())
	{
		return _spanLists[K].PopFront();
	}
	//查看第K个桶的后面的桶是否有span(K+1:跳过当前没有span的桶)
	for (size_t i = K + 1; i < NPAGES; i++)
	{
		if (!_spanLists[i].Empty())
		{
			//切分span
			Span* Nspan = _spanLists[i].PopFront();
			//Span* Kspan = new Span;
			Span* Kspan = _spanPool.New();
			//起始页号
			Kspan->_pageId = Nspan->_pageId;
			//页数
			Kspan->_n = K;
			Nspan->_pageId += K;
			Nspan->_n -= K;
			//将切分剩下的页缓存重新挂起来
			_spanLists[Nspan->_n].PushFront(Nspan);

			//建立id和span的映射,方便centralcache回收小块内存,查找对应的span 
			for (PAGE_ID i = 0; i < Kspan->_n; i++)
			{
				_idSpanMap[Kspan->_pageId + i] = Kspan;
			}
			return Kspan;
		}
	}
	//其余桶为空,此时向(堆)系统申请一个128Page的内存块
	//Span* newBigSpan = new Span;
	Span* newBigSpan = _spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	newBigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	newBigSpan->_n = NPAGES - 1;
	//在pagecache对应的桶中插入刚申请的内存span
	_spanLists[newBigSpan->_n].PushFront(newBigSpan);
	//复用自己,重新进行切分
	return NewSpan(K);
}

由此我们就可以根据内存对象的地址转换成页号,在unordered_map当中找到所对应的span了
如果存在页号所对应span的映射,返回span,否则直接结束程序,因为在获取一个span的时候,我们就已经对其进行映射,如果不存在,只能说明代码逻辑有问题

《PageCache.cpp》
Span* PageCache::MapObjectToSpan(void* obj)
{
	PAGE_ID id = ((PAGE_ID)obj >> PAGE_SHIFT);
	auto ret = _idSpanMap.find(id);
	if (ret != _idSpanMap.end())
	{
		return ret->second;
	}
	else 
	{
		assert(false);
		return nullptr;
	}
}

遍历自由链表上的内存对象,并获取其与span的映射关系,然后再头插到对应的span管理的自由链表上,每次归还一个内存对象,就对该内存对象所属的span结构中的_usecount进行–,如果减到0,说明这个span分配出去的内存对象全部归还回来了,此时就可以将此span进一步归还给下一层page cache

《CentralCache.cpp》
void CentralCache::ReleaseListToSpans(void*& start, size_t size)
{ 
	size_t index = AlignmentRules:: Index(size);
	//将threadcache上的自由链表上的每一个内存对象归还给对应的span中
	_spanLists[index]._mtx.lock();
	while (start)
	{
		//保存下一个
		void* next = FreeList::NextObj(start);
		//获取span与自由链表中内存对象的映射关系
		Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
		//头插
		FreeList::NextObj(start) = span->_freeList;
		span->_freeList = start;
		span->_useCount--;		//更新分配给threadcache的计数
		//全部内存对象已经归还,此时可以将span归还给下一层pagecache中
		if (span->_useCount == 0)
		{
			//将此span从原有的链表中解除
			_spanLists[index].Erase(span);
			span->_freeList = nullptr;
			span->_prev = nullptr;
			span->_next = nullptr;
			//解桶锁,因为此span要返还给下一层,已经不用了
			_spanLists[index]._mtx.unlock();
			PageCache::GetInstance()->_pageMutex.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(span);
			PageCache::GetInstance()->_pageMutex.unlock();
			_spanLists[index]._mtx.lock();
		}

		//进行下一个内存对象的归还
		start = next;
	}
	_spanLists[index]._mtx.unlock();
}

值得一提的是,当_usecount为0的时候,此时就需要把某个span归还给下一层,首先应该把该span在centralcache中
对应某个桶的span双链表中移除,然后将该span的自由链表置空,因为在page cache中的span是不需要切分成一个个小的内存对象的,另外该span的前后指针也是需要置空的,因为要重新插入pagecache中,需要把该span在centralcache中的前后关系清除。

三、PageCache回收内存

1、当central cache中的某个span的_useCount减到0了,此时central
cache就需要把此span返还给下一层page cache。
2、为了缓解内存碎片问题,pagecache还需要将此span与其余空闲的span进行合并,合并分为向前与向后合并

向前合并:
向前合并的时候,需要判断第x-1页的span是否空闲,如果空闲就进行合并,合并后还需要继续向前进行合并,直到不能合并为止
在这里插入图片描述
向后合并:
向后合并的时候,需要判断第x+_n页的span是否空闲,如果空闲就进行合并,合并后还需要继续向后进行合并,直到不能合并为止
在这里插入图片描述

1、在向前合并的时候,我们会用当前页号-1得到上一个span的末尾页号,在centralcache回收内存中,我们对每一个从pagecache申请的span与其页号一一映射,为的就是让threadcache中每一个内存对象能通过内存对象的起始地址算出页号找到对应的span,好归还到每一个centralcache的span当中,而在pagecache回收内存中,我们需要建立一个span的首尾页号与其span的映射,方便向前与向后合并
2、因此当我们申请k页的span时,如果是将n页的span切成了一个k页的span和一个n-k页的span,我们除了需要建立k页span中每个页与该span之间的映射关系之外,还需要建立剩下的n-k页的span与其首尾页之间的映射关系。
3、并且我们需要一个_isUse成员变量来判断前后span是否正在被使用

增加_isUse变量

//管理以页为单位的大块内存
《Common.h》
struct Span
{
	PAGE_ID _pageId = 0;        //大块内存起始页的页号
	size_t _n = 0;              //页的数量

	Span* _next = nullptr;      //双链表结构
	Span* _prev = nullptr;

	size_t _useCount = 0;       //切好的小块内存,被分配给thread cache的计数
	void* _freeList = nullptr;  //切好的小块内存的自由链表

	bool _isUse = false;        //是否在被使用
};

对一个span的首位页号进行映射

//在pagecache中获取一个n页的span
《PageCache.cpp》
Span* PageCache::NewSpan(size_t K)
{
	assert(K > 0);
	//大于128页的直接向对进行申请 
	if (K > NPAGES - 1)
	{
		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;	//建立映射关系
		return span;
	}

	//std::cout << K << std::endl;
	assert(K > 0 && K < NPAGES);
	//检查pagecache第K个桶是否有span
	if (!_spanLists[K].Empty())
	{
		return _spanLists[K].PopFront();
	}
	//查看第K个桶的后面的桶是否有span(K+1:跳过当前没有span的桶)
	for (size_t i = K + 1; i < NPAGES; i++)
	{
		if (!_spanLists[i].Empty())
		{
			//切分span
			Span* Nspan = _spanLists[i].PopFront();
			//Span* Kspan = new Span;
			Span* Kspan = _spanPool.New();
			//起始页号
			Kspan->_pageId = Nspan->_pageId;
			//页数
			Kspan->_n = K;
			Nspan->_pageId += K;
			Nspan->_n -= K;
			//将切分剩下的页缓存重新挂起来
			_spanLists[Nspan->_n].PushFront(Nspan);
			//存储Nspan的首位页号与Nspan映射,方便page cache回收内存时进行合并查找(合并的时候只用找首位页号
			_idSpanMap[Nspan->_pageId] = Nspan;							//首  
			_idSpanMap[Nspan->_pageId + Nspan->_n - 1] = Nspan;			//尾

			//建立id和span的映射,方便centralcache回收小块内存,查找对应的span 
			for (PAGE_ID i = 0; i < Kspan->_n; i++)
			{
				_idSpanMap[Kspan->_pageId + i] = Kspan;
			}
			return Kspan;
		}
	}
	//其余桶为空,此时向(堆)系统申请一个128Page的内存块
	//Span* newBigSpan = new Span;
	Span* newBigSpan = _spanPool.New();
	void* ptr = SystemAlloc(NPAGES - 1);
	newBigSpan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
	newBigSpan->_n = NPAGES - 1;
	//在pagecache对应的桶中插入刚申请的内存span
	_spanLists[newBigSpan->_n].PushFront(newBigSpan);
	//复用自己,重新进行切分
	return NewSpan(K);
}

在向前或向后进行合并的过程中:

1、如果没有通过页号获取到其对应的span,说明对应到该页的内存块还未申请,此时需要停止合并。
2、如果通过页号获取到了其对应的span,但该span处于被使用的状态,那我们也必须停止合并。
3、如果合并后大于128页则不能进行本次合并,因为page cache无法对大于128页的span进行管理。

向前向后合并空闲span

《PageCache.cpp》
void PageCache::ReleaseSpanToPageCache(Span* span)
{
	//如果申请的内存是从堆来的
	if (span->_n > NPAGES - 1)
	{
		void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
		SystemFree(ptr);
		//delete span;
		_spanPool.Delete(span);
		return;
	}
	while (1)
	{
		//对span前的页,尝试进行合并,缓解内存碎片问题
		PAGE_ID prevId = span->_pageId - 1;
		auto ret = _idSpanMap.find(prevId);
		//查找前一个span是否存在,若查找到尾,则不存在
		if (ret == _idSpanMap.end())
		{
			break;
		}
		Span* prevSpan = ret->second;
		//超出所合并的最大页数
		if (prevSpan->_n + span->_n > NPAGES - 1)
		{
			break;
		}
		//若span正在使用,停止合并
		if (prevSpan->_isUse == true)
		{
			break;
		}
		//合并前后页
		span->_n += ret->second->_n;
		span->_pageId = prevId;
		//将以被合并的span从链表中解除
		_spanLists[prevSpan->_n].Erase(prevSpan);
		//delete prevSpan;
		_spanPool.Delete(prevSpan);
	}

	while(1)
	{ 
		//对span后的页,尝试进行合并,缓解内存碎片问题
		PAGE_ID nextId = span->_pageId + span->_n;
		auto ret = _idSpanMap.find(nextId);
		//查找前一个span是否存在,若查找到尾,则不存在
		if (ret == _idSpanMap.end())
		{
			break;
		}
		Span* nextSpan = ret->second;
		//超出所合并的最大页数
		if (nextSpan->_n + span->_n > NPAGES - 1)
		{
			break;
		}
		//若span正在使用,停止合并
		if (nextSpan->_isUse == true)
		{
			break;
		}

		//合并前后页
		span->_n += ret->second->_n;
		//将以被合并的span从链表中解除
		_spanLists[nextSpan->_n].Erase(nextSpan);
		//delete nextSpan;
		_spanPool.Delete(nextSpan);
	}
	//插入所合并好的span
	_spanLists->PushFront(span);
	//建立该span与其首位页的映射
	_idSpanMap[span->_pageId] = span;
	_idSpanMap[span->_pageId += span->_n - 1] = span;
	//将刚合并好的span设置成未使用的状态
	span->_isUse = false;
}

小结

今日的项目分享就到这里啦,做完一部分项目一定要好好总结呀,不能贪快,如果本文存在疏漏或错误的地方,还请您能够指出

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

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

相关文章

Google视觉机器人超级汇总:从RT、RT-2到AutoRT/SARA-RT/RT-Trajectory、RT-H

前言 随着对视觉语言机器人研究的深入&#xff0c;发现Google的工作很值得深挖&#xff0c;比如RT-2 ​想到很多工作都是站在Google的肩上做产品和应用&#xff0c;​Google真是科技进步的核心推动力&#xff0c;做了大量大模型的基础设施&#xff0c;服(推荐重点关注下Googl…

C语言——关于指针运算的例题分析

1.指针运算中关于 sizeof 和 strlen 的例题分析 1. sizeof(数组名)&#xff0c;这⾥的数组名表⽰整个数组&#xff0c;计算的是整个数组的⼤⼩。 2. &数组名&#xff0c;这⾥的数组名表⽰整个数组&#xff0c;取出的是整个数组的地址。 3. 除此之外所有的数组名都表⽰…

SpringBoot之SpringBoot整合MyBatis

本章详情 使用SpringBoot和MyBatis通过注解的方式操作数据库使用SpringBoot和MyBatis通过XML配置文件的方式操作数据库 项目搭建 1. 打开idea,选择Create New Project 2.选择Spring Initializer,然后点击Next 3.填写组织&#xff0c;坐标等信息&#xff0c;然后点击Next 4.选…

信息泄露漏洞的JS整改方案

引言 &#x1f6e1;️ 日常工作中&#xff0c;我们经常会面临线上环境被第三方安全厂商扫描出JS信息泄露漏洞的情况&#xff0c;这给我们的系统安全带来了潜在威胁。但幸运的是&#xff0c;对于这类漏洞的整改并不复杂。本文将介绍几种可行的整改方法&#xff0c;以及其中一种…

LeetCode-347. 前 K 个高频元素【数组 哈希表 分治 桶排序 计数 快速选择 排序 堆(优先队列)】

LeetCode-347. 前 K 个高频元素【数组 哈希表 分治 桶排序 计数 快速选择 排序 堆&#xff08;优先队列&#xff09;】 题目描述&#xff1a;解题思路一&#xff1a;哈希表记录出现次数&#xff0c;然后用最小堆取&#xff0c;因为每次都是弹出最小的&#xff0c;剩下的一定是K…

【鸿蒙开发】系统组件Column

Column组件 Column沿垂直方向布局的容器。 接口&#xff1a; Column(value?: {space?: string | number}) 参数&#xff1a; 参数名 参数类型 必填 参数描述 space string | number 否 纵向布局元素垂直方向间距。 从API version 9开始&#xff0c;space为负数或者…

error:LNK2005 已经在*.obj中定义 的原因分析及对策

LNK2005是一个重复定义错误&#xff0c;造成LNK2005主要有以下几种情况&#xff1a; 目录 全局变量的重复定义 情况A&#xff1a;全局变量在.cpp文件中的多次声明 情况B&#xff1a;变量名重复 头文件的包含重复 解决方案 #ifndef标识符宏定义 pragma once预编译 头文件…

C++第十五弹---string基本介绍(一)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、什么是STL 2、STL的版本 3、STL的六大组件 4、STL的重要性 5、如何学习STL 6、STL的缺陷 7、为什么学习string类 7.1、C语言中的字符串…

autodl常用工具命令

以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 tar/zip命令镜像版本参考torch包全版本下载torch和cuda版本对应conda命令conda打包conda 环境重命名conda环境复制和转移conda环境删除 tar/zip命令 参考链接 文件目录打包&#x…

macU盘在电脑上读不出来 u盘mac读不出来怎么办 macu盘不能写入 Tuxera NTFS for Mac免费下载

对于Mac用户来说&#xff0c;使用U盘是很常见的操作&#xff0c;但有时候可能会遇到Mac电脑无法读取U盘的情况&#xff0c;这时候就需要使用一些特定的工具软件来帮助我们解决问题。本文就来告诉大家macU盘在电脑上读不出来是怎么回事&#xff0c;u盘mac读不出来怎么办。 一、m…

PTA题解 --- 静静的推荐(C语言)

今天是PTA题库解法讲解的第七天&#xff0c;今天我们要讲解静静的推荐&#xff0c;题目如下&#xff1a; 解题思路&#xff1a; 这个问题的核心在于如何在满足给定条件的情况下&#xff0c;最大化推荐学生的数量。首先&#xff0c;我们需要过滤出所有天梯赛成绩不低于175分的学…

3.2.k8s搭建-kubeadm

目录 一、虚拟机准备 二、所有节点环境准备 1.所有节点做hosts解析 2.所有节点重新命名 3.所有节点安装docker 4.所有节点为docker做linux内核转发 5.所有节点配置docker 6.所有节点关闭swap分区 7.所有节点验证网卡硬件编号是否冲突 8.所有节点配置允许iptables桥接…

Nginx配置文件修改结合内网穿透实现公网访问多个本地web站点

文章目录 1. 下载windows版Nginx2. 配置Nginx3. 测试局域网访问4. cpolar内网穿透5. 测试公网访问6. 配置固定二级子域名7. 测试访问公网固定二级子域名 1. 下载windows版Nginx 进入官方网站(http://nginx.org/en/download.html)下载windows版的nginx 下载好后解压进入nginx目…

水质监测站解析

TH-LSZ05水质监测站作为水资源保护和环境管理的重要工具&#xff0c;其功能和作用正日益受到人们的重视。随着科技的不断进步&#xff0c;水质监测站也在不断更新和完善&#xff0c;以适应不同场合和需求的监测任务。 首先&#xff0c;在技术创新方面&#xff0c;水质监测站正…

域控网络的创建

打进内网的方法 1.web服务器 域控创建 配置自己的ip winr&#xff1a;ncpa.cpl 然后属性将自己电脑的ip修改为静态ip 将DNS 修改为自己的ip 输入 winR 输入dcpromo 创建一个域 名为 cc.com 尝试去ping通 并将DNS 修改为自动 看看ping通的区别 在另一台电脑DNS修改为刚刚…

Android Studio 生成 keystore 签名文件及打包验证流程

一、创建keystore签名文件 1、在菜单栏中&#xff0c;依次点击 Build - Generate Signed Bundle/Apk...(生成签名) 2、选择 APK 选项&#xff0c;点击按钮 Next 到下一步 3、新建key store秘钥文件&#xff0c;点击按钮 Next 到下一步 4、按如下提示填写信息&#xff0c;点击按…

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第二套

华为海思校园招聘-芯片-数字 IC 方向 题目分享&#xff08;有参考答案&#xff09;——第二套&#xff08;共九套&#xff0c;每套四十个选择题&#xff09; 部分题目分享&#xff0c;完整版获取&#xff08;WX:didadidadidida313&#xff0c;加我备注&#xff1a;CSDN huawei…

[开源] 基于transformer的时间序列预测模型python代码

分享一下基于transformer的时间序列预测模型python代码&#xff0c;给大家&#xff0c;记得点赞哦 #!/usr/bin/env python # coding: 帅帅的笔者import torch import torch.nn as nn import numpy as np import pandas as pd import time import math import matplotlib.pyplo…

UI自动化测试重点思考(下)--装饰器/生成器/夹具的使用/描述符的作用/ddt驱动/多线程

UI自动化测试重点思考--装饰器 装饰器装饰器定义装饰器代码示例装饰器的执行如何将装饰器融合到pytest框架里面 生成器创建生成器生成器的定义如何将生成器融合到pytest框架里面 fixture&#xff08;夹具&#xff09;使用pytest fixture 中 scope 参数的详细解释 描述符的总结描…

大话设计模式——16.命令模式(Command Pattern)

简介 请求以命令的形式包裹在对象中&#xff0c;并传给调用对象。调用对象寻找可以处理该命令的对象进行执行。命令模式是一种特殊的策略模式&#xff0c;体现多个策略执行的问题&#xff0c;而不是选择的问题 UML图 应用场景 界面选择、键盘、按钮、事件操作都类似命令模式 …