当前位置: 首页 > news >正文

高并发内存池(三):TLS无锁访问以及Central Cache结构设计

目录

前言:

一,thread cache线程局部存储的实现

问题引入

概念说明

基本使用

thread cache TLS的实现

 二,Central Cache整体的结构框架

大致结构

 span结构

span结构的实现

三,Central Cache大致结构的实现

单例模式

thread cache向Central Cache申请空间的接口


前言:

在上篇文章中,我们完成了thread chche整体结构的设计。以及项目的整体框架也已经有所了解了。

对于该项目,高并发内存池:主要分为三层结构,thread cache,Central Cache以及Page Cache。对于 thread cache,每个线程独享一个thread cache,申请资源时,优先找对应的thread cache,其中涉及到内存对齐规则的映射。

本篇会用到的知识:TLS线程局部存储,单例模式,慢开始反馈调节算法。 

一,thread cache线程局部存储的实现

现在我们已经实现了thread cache的大致结构:申请空间,释放空间。

问题引入

但是现在还面临一个问题:

在多线程环境下,如何让当前的前程 只看到其对应的thread cache??其他线程的无法看到。也就是如何实现每个线程独享一个 thread cache对象???

这时就需要使用到Thread Local storage(线程局部存储),简称TLS。

概念说明

线程局部存储(TLS),是一种变量的存储方法,这个变量在它所在的线程内是全局可以访问的,但是不能被其他线程访问到,这样就保证了数据的线程独立性。而熟知的全局变量是所有线程都可以访问的,这样就不可避免需要锁来控制,从而增加了控制成本和代码复杂度。

基本使用

使用到的函数:TlsAlloc,TlsSetValue,TlsGetValue,TlsFree

当然,在使用线程局部存储时,除了使用上述Windows提供的API函数,还可以使用 Microsoft VC++ 编译器提供的如下方法定义一个线程局部变量:

__declspec(thread) int g_mydata =1

 示例:

#include <iostream>
#include <Windows.h>
#include <thread>
__declspec(thread) int g_mydata = 1;
void task1()
{while (true){++g_mydata;Sleep(1000);}
}void task2()
{int n = 10;while (n--){std::cout << "g_mydata=" << g_mydata <<",线程ID为:" << std::this_thread::get_id() << std::endl;}
}//TLS线程局部存储的使用示例
void testTLS()
{std::thread t1(task1);std::thread t2(task2);t1.join();t2.join();
}int main()
{//TestFiedMemoryPool();testTLS();return 0;
}

可以看到,一个线程 在对该数据进行修改时,另一个线程看到的数据不变。这就是线程局部存储,每个线程只能看到自己对应的数据,不能看到其他线程的。 

thread cache TLS的实现

现在通过TLS,就可以实现 每个线程独享一个thread cache,并且其他线程 无法获取到。

//线程局部存储::TLS机制
//每个线程只能看到自己的thread cahce
__declspec(thread) threadCache* pTLSThreadCache = nullptr;

刚开始,每个线程启动时,我们都是通过 thread cache对象来进行申请空间,同时释放空间的。所以,我们可以再增加两个接口,申请空间,会先找到对应 的thread cache对象,再调用其申请空间的接口。同样,释放空间也是如此。代码如下:

//相当于对thread cache做了一层封装
//申请size大小的空间
static void* ConsurrentAlloc(size_t size)
{if (pTLSThreadCache == nullptr){pTLSThreadCache = new threadCache;}return pTLSThreadCache->Allocate(size);
}//释放空间接口
static void ConcurrentDealloc(void* ptr, size_t size)
{assert(pTLSThreadCache);pTLSThreadCache->Deallocate(ptr, size);
}

 二,Central Cache整体的结构框架

Central Cache做为该项目第二层的结构,它起到均衡调度的作用。

大致结构

Central Cache的结构和thread cache的结构相似,也使用哈希桶的设计结构 。

如上图,Central Cache设计的时候,和thread cache的内存对齐规则是一样的。

为什么要这样设计???

  1. 假设thread cache中下标为n的桶为空时,在向下一层申请的时候,由于Central Cache采用相同的规则,所以此时直接去Cental Cache的下标为n的桶的申请。
  2. Central  Cache为thread cache分配内存空间,如果同时有多个线程来访问,由于Central Cache是属于所有线程,所以每个线程在申请内存空间的时候,就会存在线程安全问题,是需要加锁的。
  3. 如果两个线程访问的是同一个桶,那么就会存在锁竞争,一个线程申请完了,才能让另一个线程申请。
  4. 但是如果两个线程访问的是不同的桶,那么就不会存在锁竞争,可以认为是这两个线程是并行申请的,效率就会大大提高。
  5. 所以,Central Cache是需要加锁访问的,但是不是整体进行加锁的。而每个桶拥有一把锁,访问同一个桶时才会 存在锁的竞争。

 span结构

与thread cache结构不同的是:

  1. thread cahce中,每个桶的后面挂的是一个个的小内存块。比如按照4Byte对齐,对应桶中都是一个一个4Byte的内存块(的地址)。
  2. 而Central Cache是为每一个thread cache分配空间的,所以他所管理的内存块更大。每个哈希桶中挂的是一个一个的span。所谓span,就是管理以页为单位的大块空间。这里一页的大小按照8KB计算。
  3. 一个span,可能包含多个页,也可能包含一个页。

span如何管理这大块内存??

自由链表!!!没错,仍然是按照自由链表的方式。将这一大块内存,切分成很多个小块内存,然后使用链表的形式组织起来!!!如下图:

 每个span按照对应的对齐规则,将大块内存切分成对应的小块内存,并使用自由链表组织起来。

所以,对于一个span,可能包含多个内存块,也可能分配出去了一部分,剩余一部分,也可能全部都分配出去了,剩余为空 。

那么我们如何可以知道某个span中,分配出去多少内存???

  1. 所以,在 span结构中,我们需要增加一个变量usecount,来记录有多少内存块分配出去了。记录这个变量的目的是,当span这个结构完全被还回来的时候,我们就可以将它还给下一层了。
  2. 所以,当上层thread cache申请内存块的时候,就让对应span的usecount++。当上层thread cache归还内存块的时候,就让对应span的usecount--。
  3. 当usecount=0时,说明这个span的内存块已经全部还回来了。那么此时就可以将该内存块 返回给下一层 了。
  4. 当将span向一层返回的时候,在Central Cache中,就需要将对应哈希桶中对应的span删除。如果哈希桶中的span按照单链表的形式存储,删除操纵会比较麻烦。所以我们可以设置成双向链表的结构,删除操作的时间复杂度是O(1)

span结构的实现

通过上述部分,了解到Central Cache的大致框架后,接下来,就是各部分的代码实现。

要实现Central Cache的结构,首先就是对span结构的实现。

span——管理以页为单位的大块内存,每个span包含页的个数不同,我们需要记录一个 span有多少页,变相的就记录了这大块内存的大小。同时还需要记录起始页号。

这里一页按照8KB来计算 。

  • 如果是在32位环境下,内存大小为2^32,也就是4GB。总页数=4GB/8KB=2^32/2^13=2^19,大约一共有50多万页,使用int 可以存储。
  • 如果是在64位环境 下,内存大小为2^64,总页数=2^64/2^13=2^51,这时候使用int就存不下了。 
  • 为了解决这种问题,可以使用条件编译,如果是32位环境,使用int。如果是64位,使用long long。
  • 但是需要注意的是,在WIN32配置下,_WIN32有定义,_WIN64没有定义。在_WIN64配置下,_WIN32和_WIN64的定义都有。
//管理以页为单位的大块空间
struct span
{size_t _pageID;//该大块空间的起始页号size_t _n = 0;//页的数量span* _next = nullptr;//双向链表的结构span* _prev = nullptr;size_t _usecount = 0;//切好的小块内存,分配给thread cache的个数void* _freelist = nullptr;//管理切分好的小对象
};//Central Cache的每个哈希桶中保存的是span组成的链表
class SpanList
{
public:SpanList(){_head = new span;_head->_next = _head;_head->_prev = _head;}//在指定span前插入一个void Insert(span* pos, span* newspan){assert(pos);assert(newspan);span* prev = pos->_prev;//prev newspan posnewspan->_prev = prev;newspan->_next = pos;pos->_prev = newspan;}//从链表中删除指定的某个spanvoid Erase(span* pos){assert(pos);assert(pos != _head);span* prev = pos->_prev;span* next = pos->_next;prev->_next = next;next->_prev = prev;}
private:span* _head=nullptr;//链表的头指针
public:std::mutex _mtx;//桶锁
};

三,Central Cache大致结构的实现

Central Cache也是一个哈希桶结构,和thread cache采用一样的内存对齐规则。每个桶下面挂的是一个一个的span,而每个span内部也有一个 链表,挂的是切分好的小块内存。

单例模式

对于thread cache,它是每个线程独享的,每个线程只能看到自己的thread cache对象。

对于Central Cache,它是所有线程共享的。我们不希望未来有多个Central Cache,保证整个进程中只有一个Central Cache。所以我们可以通过单例模式来实现。

单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。

//Central Cache的结构和Thread Cache的结构相似
//Central Cache的哈希桶中挂的是一个个的span
//实现成单例模式
class CentralCache
{
public://获取单例对象static CentralCache* GetInstance(){return &_sInst;}//从 Central Cache获取一定数量的对象给thread cache//start,end为输出型参数,n表示希望获得的内存块个数,byte_size表示对应的内存块的大小size_t FetchRangeObj(void*& start, void*& end, size_t n, size_t byte_size);//禁用构造,拷贝构造,赋值重载CentralCache() = delete;CentralCache(const CentralCache&) = delete;CentralCache operator=(const CentralCache&) = delete;
private:SpanList _spanlists[NFREELISTS];static CentralCache _sInst;
};

thread cache向Central Cache申请空间的接口

当某个线程申请内存空间,当对应的桶为空时,需要向Central Cache申请。

比如一个线程来向Central Cache申请8字节的内存,Central Cache一定会分配多个8字节的内存块。

多余的会让thread cache保存,下次再申请时,就直接找thread cache,因为访问thread cache是无所的,申请内存能更快。那么Central Cache应该给返回对少个内存块???

方法:慢开始反馈调节算法

1,按照申请的内存大小来决定返回多少个内存块。

如果申请的内存比较小,比如5字节,我们可以多给几个,比如分配给50字节,返回10个 内u才能块。如果申请的内存空间比较大,比如256KB,就不能返回的太多,返回2个或者3个内存块。

所以,当申请的内存块大小为n时,我们需要知道最多给它分配多少个,也就是它的上限

2,按照使用内存的是否频繁,决定返回多少个内存块

如果给的太多,可能很多都用不上。如果给的太少,可能会导致该线程频繁的找Central Cache申请内存。

  • 线程之所以会找Central Cache申请空间,无疑是thread cache对应桶的内存用完了。
  • thread cahce有很多的桶,当频繁的为某个桶申请内存时,说明这个桶用的很频繁,我们就一次多给,比如给2倍或者3倍。
  • 但是,如何知道一个桶使用的是否频繁呢???我们可以对每个桶,也就是每个自由链表,在自由链表中增加一个变量maxSize=1,表示是否频繁申请。当这个桶第一次向Central Cache申请内存时,就给一块内存,然后让这个桶的maxSize+1,下次申请的时候 ,就给2块,依次类推......也可以将+1换成+2或者+3,这样增长的速度就会变快。当然,这里不能一直+,会有上限的。

 结合这两种情况,计算出的结果,取一个最小值,就是最后应该分配的内存块的个数。

//自由链表中头插一段区间
//start,end
void pushRange(void* start, void* end)
{NextObj(end) = _freelist;_freelist = start;
}
//向Central Cache申请内存
//index表示对应的哈希桶的下标
void* threadCache::FetchMemoryFromCental(size_t index, size_t  size)
{//首先计算需要获取多少个内存块//慢开始反馈调节算法size_t batchNum = min(SizeClass::NumMoveSize(size), _freelists[index].MaxSize());//保证batchNum不超过上限if (_freelists[index].MaxSize() == batchNum){_freelists[index].MaxSize()++;}void* start = nullptr;void* end = nullptr;//调用Central Cache接口,返回获取到的内存块的个数//start和end是输出型参数,表示 得到的内存块的起始地址和结束地址//这里actual表示实际得到的内存块的个数// 因为Central Cache的内存块可能不够batchNum个,只是将所有的都返回了size_t actual = CentralCache::GetInstance()->FetchRangeObj(start,end,batchNum,size);assert(actual > 1);//如果只返回了一个内存块,将该内存块直接返回给上层使用if (actual == 1){assert(start == nullptr);return start;}else{//先将start+1到end范围的内存块,保存在对应的哈希桶中//再将start返回给上层使用_freelists[index].pushRange(NextObj(start), end);return start;}
}

接下来就是要完成 Central Cache给thread cache分配内存的接口了。也就是FetchRangeObj(start,end,batchNum,size)的接口了。

Central Cache的结构如下图 :每个span管理的是以页为单位的大块内存。一页的大小是8KB。同时每个span内部是切分好的小块内存,以链表的形式管理起来。

 我们现在已经计算出:thread cache找Central Cache申请内存时,Central Cache应该分配batchNum个内存块给thread cache。

也就是从对应的哈希桶的某个span中切出batchNum个内存块。但是由于可能之前有多个线程来申请,导致现在有的span为空,有的span有内存块,但是可能不够batchNum个。所以我们实际给的个数可能小于期望获得的个数的。

实现思路:首先找到对应的哈希桶,遍历spanlist链表,找到一个 非空的span。spanlist链表是双向循环带头链表,为了方便遍历,我们可以使用类似于迭代器的实际思路,封装一层。代码如下:

注意:在查找spanlist获取一个非空的span时,可能整个spanlist都为空,此时就需要向下一层Page Cache申请。(这部分代码先不实现,Page Cache实现之后完成该部分)。

	span* Begin(){return _head->_next;}span* End(){return _head;}

获取到span之后,就可以遍历span中的_freelist,从中申请batchNum个内存块,如果不够,有多少申请多少。如下图所示:

 上述情况是span中内存块的个数足够,可能存在不够的情况,所以在end向后移动 的时候,需要判断end不能为空。

//从对应的哈希桶,也就是spanlist中,获取一个非空的span
span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{//...return nullptr;
}//从 Central Cache获取一定数量的对象给thread cache
//start,end为输出型参数,n表示希望获得的内存块个数,size表示对应的内存块的大小
//返回值表示实际获得的内存块的个数
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t n, size_t size)
{//申请的内存块大小为size,先找到对应的哈希桶size_t index = SizeClass::Index(size);//多线程可能会访问同一个桶,需要加锁_spanlists[index]._mtx.lock();//在对应的桶中找到一个非空的spanspan* sp = CentralCache::GetInstance()->GetOneSpan(_spanlists[index], size);assert(sp);assert(sp->_freelist);//从sp中获取n个内存块//start指向第一个内存块,end指向最后一个内存块start = sp->_freelist;end = start;//end向后走n-1步,执行最后一个内存块,但是可能不够n个,需要判空size_t i = 0;size_t actualNum = 1;//记录实际获取到的内存块的个数while (end!=nullptr&&i < n - 1){end = NextObj(end);i++;}//_freelist指向end的下一个内存块sp->_freelist = NextObj(end);//将end与下一个内存块断开连接NextObj(end) = nullptr;_spanlists[index]._mtx.unlock();//返回实际获得的内存块的个数return actualNum;
}

源码:

ConcurrentMemoryPool · 小鬼/高并发内存池 - 码云 - 开源中国 

本节完!!!

相关文章:

高并发内存池(三):TLS无锁访问以及Central Cache结构设计

目录 前言&#xff1a; 一&#xff0c;thread cache线程局部存储的实现 问题引入 概念说明 基本使用 thread cache TLS的实现 二&#xff0c;Central Cache整体的结构框架 大致结构 span结构 span结构的实现 三&#xff0c;Central Cache大致结构的实现 单例模式 thr…...

数据治理域——数据治理体系建设

摘要 本文主要介绍了数据治理系统的建设。数据治理对企业至关重要&#xff0c;其动因包括应对数据爆炸增长、提升内部管理效率、支撑复杂业务需求、加强风险防控与合规管理以及实现数字化转型战略。其核心目的是提升数据质量、统一数据标准、优化数据资产管理、支撑业务发展和…...

数据库实验报告 SQL SERVER 2008的基本操作 1

实验报告&#xff08;第 1 次&#xff09; 实验名称 SQL SERVER 2008的基本操作 实验时间 9月14日1-2节 一、实验内容 数据库的基本操作:包括创建、修改、附加、分离和删除数据库等。 二、源程序及主要算法说明 本次实验不涉及程序和算法。 三、测…...

基于STM32、HAL库的ICP-20100气压传感器 驱动程序设计

一、简介: ICP-20100 是 InvenSense(TDK 集团旗下公司)生产的一款高精度数字气压传感器,专为需要精确测量气压和海拔高度的应用场景设计。它具有低功耗、高精度、快速响应等特点,非常适合物联网、可穿戴设备和无人机等应用。 二、硬件接口: ICP-20100 引脚STM32L4XX 引脚…...

提示工程实战指南:Google白皮书关键内容一文讲清

You don’t need to be a data scientist or a machine learning engineer – everyone can writea prompt. 一、概述 Google于2025年2月发布的《Prompt Engineering》白皮书系统阐述了提示工程的核心技术、实践方法及挑战应对策略。该文档由Lee Boonstra主编&#xff0c;多位…...

国产大模型「五强争霸」:决战AGI,谁主沉浮?

引言 中国AI大模型市场正经历一场史无前例的洗牌&#xff01;曾经“百模混战”的局面已落幕&#xff0c;字节、阿里、阶跃星辰、智谱和DeepSeek五大巨头强势崛起&#xff0c;形成“基模五强”新格局。这场竞争不仅是技术实力的较量&#xff0c;更是资源、人才与生态的全面博弈。…...

Linux进程10-有名管道概述、创建、读写操作、两个管道进程间通信、读写规律(只读、只写、读写区别)、设置阻塞/非阻塞

目录 1.有名管道 1.1概述 1.2与无名管道的差异 2.有名管道的创建 2.1 直接用shell命令创建有名管道 2.2使用mkfifo函数创建有名管道 3.有名管道读写操作 3.1单次读写 3.2多次读写 4.有名管道进程间通信 4.1回合制通信 4.2父子进程通信 5.有名管道读写规律&#xff…...

高吞吐与低延迟的博弈:Kafka与RabbitMQ数据管道实战指南

摘要 本文全面对比Apache Kafka与RabbitMQ在数据管道中的设计哲学、核心差异及协同方案。结合性能指标、应用场景和企业级实战案例&#xff0c;揭示Kafka在高吞吐流式处理中的优势与RabbitMQ在复杂路由和低延迟传输方面的独特特点&#xff1b;介绍了使用Java生态成熟第三方库&…...

C++23 views::slide (P2442R1) 深入解析

文章目录 引言C20 Ranges库回顾什么是Rangesstd::views的作用 views::slide 概述基本概念原型定义辅助概念工作原理代码示例输出结果 views::slide 的应用场景计算移动平均值查找连续的子序列 总结 引言 在C的发展历程中&#xff0c;每一个新版本都会带来一系列令人期待的新特…...

SpringDataRedis的入门案例,以及RedisTemplate序列化实现

目录 SpringDataRedis 简单介绍 入门案例 RedisTemplate序列化方案 方案一: 方案二: SpringDataRedis 简单介绍 提供了对不同Redis客户端的整合(Lettuce和Jedis) 提供了RedisTemplate统一API来操作Redis 支持Redis的发布订阅模型 支持Redis哨兵和Redis集群 支持基于…...

鸿蒙HarmonyOS list优化一: list 结合 lazyforeach用法

list列表是开发中不可获取的&#xff0c;非常常用的组件&#xff0c;使用过程中会需要不断的优化&#xff0c;接下来我会用几篇文章进行list在纯原生的纯血鸿蒙的不断优化。我想进大厂&#xff0c;希望某位大厂的看到后能给次机会。 首先了解一下lazyforeach&#xff1a; Laz…...

【Jenkins简单自动化部署案例:基于Docker和Harbor的自动化部署流程记录】

摘要 本文记录了作者使用Jenkins时搭建的一个简单自动化部署案例&#xff0c;涵盖Jenkins的Docker化安装、Harbor私有仓库配置、Ansible远程部署等核心步骤。通过一个SpringBoot项目 (RuoYi) 的完整流程演示&#xff0c;从代码提交到镜像构建、推送、滚动更新&#xff0c;逐步实…...

【愚公系列】《Manus极简入门》034-跨文化交流顾问:“文化桥梁使者”

&#x1f31f;【技术大咖愚公搬代码&#xff1a;全栈专家的成长之路&#xff0c;你关注的宝藏博主在这里&#xff01;】&#x1f31f; &#x1f4e3;开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主&#xff01; &#x1f…...

数字滤波器应用介绍

此示例说明如何设计、分析数字过滤器并将其应用于数据。它将帮助您回答以下问题: 如何补偿滤波器引入的延迟?如何避免使信号失真?如何从信号中删除不需要的内容?如何微分信号?以及积分信号文章目录 补偿筛选引入的延迟补偿恒定滤波器延迟 如FIR引起的消除方法,末尾添零补…...

木马查杀篇—Opcode提取

【前言】 介绍Opcode的提取方法&#xff0c;并探讨多种机器学习算法在Webshell检测中的应用&#xff0c;理解如何在实际项目中应用Opcode进行高效的Webshell检测。 Ⅰ 基本概念 Opcode&#xff1a;计算机指令的一部分&#xff0c;也叫字节码&#xff0c;一个php文件可以抽取出…...

栈和队列复习(C语言版)

目录 一.栈的概念 二.栈的实现 三.队列的概念 四.队列的实现 五.循环队列的实现 一.栈的概念 可以将栈抽象地理解成羽毛球桶&#xff0c;或者理解成坐直升电梯&#xff1b;最后一个进去的&#xff0c;出来时第一个出来&#xff0c;并且只有一个出入口。这边需要注意的是&am…...

SDK does not contain ‘libarclite‘ at the path

Xcode16以上版本更新SDK之后就报错了。是因为缺少libarclite_iphoneos.a文件。所以需要在网上找一下该文件根据路径添加进去&#xff0c;arc文件可能需要新建一下。 clang: error: SDK does not contain ‘libarclite’ at the path ‘/Applications/Xcode.app/Contents/Develo…...

Kotlin跨平台Compose Multiplatform实战指南

Kotlin Multiplatform&#xff08;KMP&#xff09;结合 Compose Multiplatform 正在成为跨平台开发的热门选择&#xff0c;它允许开发者用一套代码构建 Android、iOS、桌面&#xff08;Windows/macOS/Linux&#xff09;和 Web 应用。以下是一个实战指南&#xff0c;涵盖核心概念…...

Oracle数据库全局性HANG的处理过程

如果Oracle数据库全局性HANG&#xff0c;首先要做的就是收集数据库HANG时的状态&#xff0c;只有收集到了相应状态&#xff0c;抓住故障现场&#xff0c;才可以进一步分析故障产生的可能原因。 出现此故障&#xff0c;一般情况下可以如此处理&#xff1a; 如果数据库是单节点&a…...

MySQL 8.0 OCP(1Z0-908)英文题库(21-30)

目录 第21题题目分析正确答案 第22题题目分析正确答案 第23题题目分析正确答案 第24题题目分析正确答案 第25题题目分析正确答案 第26题题目分析正确答案 第27题题目分析正确答案 第28题题目分析正确答案 第29题题目分析正确答案 第30题题目解析正确答案 第21题 Choose three.…...

beyond compare 免密钥进入使用(删除注册表)

beyond compare 免密钥进入&#xff0c;免费使用&#xff08;删除注册表&#xff09; 温馨提醒&#xff1a;建议仅个人使用&#xff0c;公司使用小心律师函警告&#xff01; 1.winr 输入regedit 打开注册表 2.删除计算机 \HKEY_CURRENT_USER\Software\Scooter Software\Beyo…...

前端项目2-01:个人简介页面

目录 一.代码显示 二.效果图 三.代码分析 1. 文档声明和 HTML 基本结构 2. CSS 样式部分 全局样式 body 样式 页面主要容器 box 样式 左侧区域 l 样式 右侧区域 r 样式 左侧区域中头像容器 to 样式 头像图片样式及悬停效果 左侧区域中个人信息容器 tit 样式 个人…...

.NET 8 API 实现websocket,并在前端angular实现调用

.NET 8 API 实现websocket&#xff0c;并在前端angular实现调用。 后端&#xff1a;.NET 8 WebSocket API 实现 在 .NET 8 中&#xff0c;可以通过 Microsoft.AspNetCore.WebSockets 提供的支持来实现 WebSocket 功能。以下是创建一个简单的 WebSocket 控制器的步骤。 安装必…...

P2P架构

P2P 是 Peer-to-Peer&#xff08;点对点&#xff09; 的缩写&#xff0c;是一种 去中心化 的网络架构&#xff0c;其中每个节点&#xff08;称为 “对等节点”&#xff0c;Peer&#xff09;既是 “客户端”&#xff0c;也是 “服务器”&#xff0c;可以直接与其他节点通信、共享…...

菊厂0510面试手撕题目解答

题目 输入一个整数数组&#xff0c;返回该数组中最小差出现的次数。 示例1&#xff1a;输入&#xff1a;[1,3,7,5,9,12]&#xff0c;输出&#xff1a;4&#xff0c;最小差为2&#xff0c;共出现4次&#xff1b; 示例2&#xff1a;输入&#xff1a;[90,98,90,90,1,1]&#xf…...

【25软考网工】第六章(4)VPN虚拟专用网 L2TP、PPTP、PPP认证方式;IPSec、GRE

博客主页&#xff1a;christine-rr-CSDN博客 ​专栏主页&#xff1a;软考中级网络工程师笔记 ​​ 大家好&#xff0c;我是christine-rr !目前《软考中级网络工程师》专栏已经更新二十多篇文章了&#xff0c;每篇笔记都包含详细的知识点&#xff0c;希望能帮助到你&#xff01…...

C语言:深入理解指针(3)

目录 一、数组名的理解 二、用指针访问数组 三、一维数组传参的本质 四、冒泡排序 五、二级指针 六、指针数组 七、指针数组模拟二维数组 八、结语 一、数组名的理解 数组名其实就是首元素的地址 int arr[3] {1,2,3}; printf("arr :%p\n" ,arr); printf(…...

R语言实战第5章(1)

第一部分&#xff1a;数学、统计和字符处理函数 数学和统计函数&#xff1a;R提供了丰富的数学和统计函数&#xff0c;用于执行各种计算和分析。这些函数可以帮助用户快速完成复杂的数学运算、统计分析等任务&#xff0c;例如计算均值、方差、相关系数、进行假设检验等。字符处…...

Lodash isEqual 方法源码实现分析

Lodash isEqual 方法源码实现分析 Lodash 的 isEqual 方法用于执行两个值的深度比较&#xff0c;以确定它们是否相等。这个方法能够处理各种 JavaScript 数据类型&#xff0c;包括基本类型、对象、数组、正则表达式、日期对象等&#xff0c;并且能够正确处理循环引用。 1. is…...

探索边缘计算:赋能物联网的未来

摘要 随着物联网&#xff08;IoT&#xff09;技术的飞速发展&#xff0c;越来越多的设备接入网络&#xff0c;产生了海量的数据。传统的云计算模式在处理这些数据时面临着延迟高、带宽不足等问题&#xff0c;而边缘计算的出现为解决这些问题提供了新的思路。本文将深入探讨边缘…...

Ubuntu中配置【Rust 镜像源】

本篇主要记录Ubuntu中配置Rust编程环境时&#xff0c;所需要做的镜像源相关的配置 无法下载 Rust 工具链 通过环境变量指定 Rust 的国内镜像源&#xff08;如中科大或清华源&#xff09;。 方法一&#xff1a;临时设置镜像 export RUSTUP_DIST_SERVERhttps://mirrors.ustc.e…...

netty 客户端发送消息服务端收到消息无法打印,springBoot配合 lombok使用@Slf4j

netty 客户端发送消息服务端收到消息无法打印&#xff0c;springBoot配合 lombok使用Slf4j 服务端代码 Slf4j public class EventLoopServer {public static void main(String[] args) throws InterruptedException {new ServerBootstrap().group(new NioEventLoopGroup()).c…...

学习笔记:黑马程序员JavaWeb开发教程(2025.4.3)

12.1 基础登录功能 EmpService中的login方法&#xff0c;是根据接收到的用户名和密码&#xff0c;查询时emp数据库中的员工信息&#xff0c;会返回一个员工对象。使用了三元运算符来写返回 Login是登录&#xff0c;是一个业务方法&#xff0c;mapper接口是持久层&#xff0c;是…...

Spark SQL 运行架构详解(专业解释+番茄炒蛋例子解读)

1. 整体架构概览 Spark SQL的运行过程可以想象成一个"SQL查询的加工流水线"&#xff0c;从原始SQL语句开始&#xff0c;经过多个阶段的处理和优化&#xff0c;最终变成分布式计算任务执行。主要流程如下&#xff1a; SQL Query → 解析 → 逻辑计划 → 优化 → 物理…...

【时时三省】(C语言基础)字符数组的输入输出

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 字符数组的输入输出可以有两种方法。 ( 1 )逐个字符输入输出。用格式符“% c”输入或输出一个字符. ( 2 )将整个字符串一次输入或输出。用“% s”格式符&#xff0c;意思是对字符串( strin…...

Hive HA配置高可用

Hive的高可用性(HA)通过消除关键组件的单点故障来实现,确保系统在部分故障时仍能正常运行。其基本原理涉及以下核心组件和策略: ‌1. Hive Metastore 的高可用‌ ‌ 多实例部署‌:部署多个Metastore服务实例,每个实例连接到共享的后端数据库(如MySQL、PostgreSQ…...

Python爬虫第20节-使用 Selenium 爬取小米商城空调商品

目录 前言 一、 本文目标 二、环境准备 2.1 安装依赖 2.2 配置 ChromeDriver 三、小米商城页面结构分析 3.1 商品列表结构 3.2 分页结构 四、Selenium 自动化爬虫实现 4.1 脚本整体结构 4.2 代码实现 五、关键技术详解 5.1 Selenium 启动与配置 5.2 页面等待与异…...

重构金融数智化产业版图:中电金信“链主”之道

近日&#xff0c;《商学院》杂志独家专访了中电金信常务副总经理&#xff08;主持经营工作&#xff09;冯明刚&#xff0c;围绕“金融科技”“数字底座”“架构转型”“AI驱动”等议题&#xff0c;展开了一场关于未来架构、技术变革与系统创新的深入对话。 当下&#xff0c;数字…...

笔记本电脑升级实战手册【扩展篇1】:flash id查询硬盘颗粒

文章目录 前言&#xff1a;一、硬盘颗粒介绍1、MLC&#xff08;Multi-Level Cell&#xff09;2、TLC&#xff08;Triple-Level Cell&#xff09;3、QLC&#xff08;Quad-Level Cell&#xff09; 二、硬盘与主控1、主控介绍2、主流主控厂家 三 、硬盘颗粒查询使用flash id工具查…...

文档外发安全:企业数据防护的最后一道防线

在当今数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。随着网络安全威胁日益增多&#xff0c;企业安装专业加密软件已从"可选"变为"必选"。本文将全面分析企业部署华途加密解决方案后获得的各项战略优势。 一、数据安全防护升级 核心数据全面保护…...

springboot集成langchain4j实现票务助手实战

前言 看此篇的前置知识为langchain4j整合springboot&#xff0c;以及springboot集成langchain4j记忆对话。 Function-Calls介绍 langchain4j 中的 Function Calls&#xff08;函数调用&#xff09;是一种让大语言模型&#xff08;LLM&#xff09;与外部工具&#xff08;如 A…...

ZYNQ笔记(二十一): VDMA HDMI 彩条显示

版本&#xff1a;Vivado2020.2&#xff08;Vitis&#xff09; 任务&#xff1a;实现驱动 HDMI 显示彩条图像&#xff0c;同时支持输出给 HDMI 的图像分辨率可调。 目录 一、介绍 二、硬件设计 &#xff08;1&#xff09;DVI_Transmitter &#xff08;2&#xff09;Clockin…...

常用的maven插件及其使用指南

目录 1.maven官方插件列表2.两种方式调用maven插件3.常用的maven插件总结参考文献 1.maven官方插件列表 groupId为org.apache.maven.pluginshttp://maven.apache.org/plugins/index.html 2.两种方式调用maven插件 将插件目标与生命周期阶段绑定&#xff0c;例如maven默认将m…...

Meilisearch 安装

1.环境 rockey linux 9.2 meilisearch-linux-amd64 2.下载 访问&#xff1a;https://github.com/meilisearch/meilisearch/releases 下载适合自己系统版本的。 注意&#xff1a;我下载的不是最新版本的&#xff0c;因为最新版本的需要GLIBC2.35&#xff0c;我本地系统的是…...

用postman的时候如何区分服务器还是自己的问题?

作为测试人员,在使用Postman进行接口测试时,准确判断问题是出在服务器端还是本地环境非常重要。以下是一些实用的区分方法: 1. 基础检查方法 本地问题排查清单: ✅ 检查网络连接是否正常 ✅ 确认Postman版本是否为最新 ✅ 验证请求URL是否正确(特别是环境变量是否被正确…...

【Python算法】最长递增子序列

题目链接 方法1&#xff1a; 记忆化搜索 class Solution:def lengthOfLIS(self, nums: List[int]) -> int:cachedef dfs(i):res0 for j in range(i):if nums[j]<nums[i]:res max(res,dfs(j))return res1 # 返回res表示以nums[i]结尾的LIS长度return max(dfs(i) for i…...

springboot-web基础

21.web spring MVC 基于浏览器的 B/S 结构应用十分流行。Spring Boot 非常适合 Web 应用开发。可以使用嵌入式 Tomcat、Jetty、 Undertow 或 Netty 创建一个自包含的 HTTP 服务器。一个 Spring Boot 的 Web 应用能够自己独立运行&#xff0c;不依赖需 要安装的 Tomcat&#x…...

解构赋值

【系统学习ES6】 本专题旨在对ES6的常用技术点进行系统性梳理&#xff0c;帮助大家对其有更好的掌握&#xff0c;希望大家有所收获。 ES6允许按照一定模式&#xff0c;从数组和对象中提取值&#xff0c;对变量进行赋值&#xff0c;这被称为解构。解构是一种打破数据结构&#x…...

Leetcode-BFS问题

LeetCode-BFS问题 1.Floodfill问题 1.图像渲染问题 [https://leetcode.cn/problems/flood-fill/description/](https://leetcode.cn/problems/flood-fill/description/) class Solution {public int[][] floodFill(int[][] image, int sr, int sc, int color) {//可以借助另一…...

AI 时代 UI 设计的未来范式

在人工智能技术持续突破的浪潮下&#xff0c;UI 设计领域正经历着前所未有的变革。AI 的深度介入不仅重塑了设计流程&#xff0c;更催生了全新的设计范式&#xff0c;为用户带来颠覆式的交互体验。探索 AI 时代 UI 设计的未来范式&#xff0c;是把握行业发展趋势的关键所在。​…...