鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据

基本概念

  • 队列又称消息队列,是一种常用于任务间通信的数据结构。队列接收来自任务或中断的不固定长度消息,并根据不同的接口确定传递的消息是否存放在队列空间中。

  • 任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为0,则不会挂起任务,接口会直接返回,这就是非阻塞模式。

  • 消息队列提供了异步处理机制,允许将一个消息放入队列,但不立即处理。同时队列还有缓冲消息的作用。

  • 队列用于任务间通信,可以实现消息的异步处理。同时消息的发送方和接收方不需要彼此联系,两者间是解耦的。

队列特性

  • 消息以先进先出的方式排队,支持异步读写。
  • 读队列和写队列都支持超时机制。
  • 每读取一条消息,就会将该消息节点设置为空闲。
  • 发送消息类型由通信双方约定,可以允许不同长度(不超过队列的消息节点大小)的消息。
  • 一个任务能够从任意一个消息队列接收和发送消息。
  • 多个任务能够从同一个消息队列接收和发送消息。
  • 创建队列时所需的队列空间,默认支持接口内系统自行动态申请内存的方式,同时也支持将用户分配的队列空间作为接口入参传入的方式。

消息队列长什么样?

#ifndef LOSCFG_BASE_IPC_QUEUE_LIMIT
#define LOSCFG_BASE_IPC_QUEUE_LIMIT 1024 //队列个数
#endif
LITE_OS_SEC_BSS LosQueueCB *g_allQueue = NULL;//消息队列池
LITE_OS_SEC_BSS STATIC LOS_DL_LIST g_freeQueueList;//空闲队列链表,管分配的,需要队列从这里申请typedef struct {UINT8 *queueHandle; /**< Pointer to a queue handle */	//指向队列句柄的指针UINT16 queueState; /**< Queue state */	//队列状态UINT16 queueLen; /**< Queue length */	//队列中消息总数的上限值,由创建时确定,不再改变UINT16 queueSize; /**< Node size */		//消息节点大小,由创建时确定,不再改变,即定义了每个消息长度的上限.UINT32 queueID; /**< queueID */			//队列IDUINT16 queueHead; /**< Node head */		//消息头节点位置(数组下标)UINT16 queueTail; /**< Node tail */		//消息尾节点位置(数组下标)UINT16 readWriteableCnt[OS_QUEUE_N_RW]; /**< Count of readable or writable resources, 0:readable, 1:writable *///队列中可写或可读消息数,0表示可读,1表示可写LOS_DL_LIST readWriteList[OS_QUEUE_N_RW]; /**< the linked list to be read or written, 0:readlist, 1:writelist *///挂的都是等待读/写消息的任务链表,0表示读消息的链表,1表示写消息的任务链表LOS_DL_LIST memList; /**< Pointer to the memory linked list */	//@note_why 这里尚未搞明白是啥意思 ,是共享内存吗?
} LosQueueCB;//读写队列分离

解读

  • 和进程,线程,定时器一样,消息队列也由全局统一的消息队列池管理,池有多大?默认是1024
  • 鸿蒙内核对消息的总个数有限制,queueLen消息总数的上限,在创建队列的时候需指定,不能更改.
  • 对每个消息的长度也有限制, queueSize 规定了消息的大小,也是在创建的时候指定.
  • 为啥要指定总个数和每个的总大小,是因为内核一次性会把队列的总内存(queueLen*queueSize)申请下来,确保不会出现后续使用过程中内存不够的问题出现,但同时也带来了内存的浪费,因为很可能大部分时间队列并没有跑满.
  • 队列的读取由queueHeadqueueTail管理,Head表示队列中被占用的消息节点的起始位置。Tail表示被占用的消息节点的结束位置,也是空闲消息节点的起始位置。队列刚创建时,Head和Tail均指向队列起始位置
  • 写队列时,根据readWriteableCnt[1]判断队列是否可以写入,不能对已满(readWriteableCnt[1]为0)队列进行写操作。写队列支持两种写入方式:向队列尾节点写入,也可以向队列头节点写入。尾节点写入时,根据Tail找到起始空闲消息节点作为数据写入对象,如果Tail已经指向队列尾部则采用回卷方式。头节点写入时,将Head的前一个节点作为数据写入对象,如果Head指向队列起始位置则采用回卷方式。
  • 读队列时,根据readWriteableCnt[0]判断队列是否有消息需要读取,对全部空闲(readWriteableCnt[0]为0)队列进行读操作会引起任务挂起。如果队列可以读取消息,则根据Head找到最先写入队列的消息节点进行读取。如果Head已经指向队列尾部则采用回卷方式。
  • 删除队列时,根据队列ID找到对应队列,把队列状态置为未使用,把队列控制块置为初始状态。如果是通过系统动态申请内存方式创建的队列,还会释放队列所占内存。
  • 留意readWriteList,这又是两个双向链表, 双向链表是内核最重要的结构体,牢牢的寄生在宿主结构体上.readWriteList上挂的是未来读/写消息队列的任务列表.

初始化队列

LITE_OS_SEC_TEXT_INIT UINT32 OsQueueInit(VOID)//消息队列模块初始化
{LosQueueCB *queueNode = NULL;UINT32 index;UINT32 size;size = LOSCFG_BASE_IPC_QUEUE_LIMIT * sizeof(LosQueueCB);//支持1024个IPC队列/* system resident memory, don't free */g_allQueue = (LosQueueCB *)LOS_MemAlloc(m_aucSysMem0, size);//常驻内存if (g_allQueue == NULL) {return LOS_ERRNO_QUEUE_NO_MEMORY;}(VOID)memset_s(g_allQueue, size, 0, size);//清0LOS_ListInit(&g_freeQueueList);//初始化空闲链表for (index = 0; index < LOSCFG_BASE_IPC_QUEUE_LIMIT; index++) {//循环初始化每个消息队列queueNode = ((LosQueueCB *)g_allQueue) + index;//一个一个来queueNode->queueID = index;//这可是 队列的身份证LOS_ListTailInsert(&g_freeQueueList, &queueNode->readWriteList[OS_QUEUE_WRITE]);//通过写节点挂到空闲队列链表上}//这里要注意是用 readWriteList 挂到 g_freeQueueList链上的,所以要通过 GET_QUEUE_LIST 来找到 LosQueueCBif (OsQueueDbgInitHook() != LOS_OK) {//调试队列使用的.return LOS_ERRNO_QUEUE_NO_MEMORY;}return LOS_OK;
}

解读

  • 初始队列模块,对几个全局变量赋值,创建消息队列池,所有池都是常驻内存,关于池后续有专门的文章整理,到目前为止已经解除到了进程池,任务池,定时器池,队列池,==
  • LOSCFG_BASE_IPC_QUEUE_LIMIT个队列挂到空闲链表g_freeQueueList上,供后续分配和回收.熟悉内核全局资源管理的对这种方式应该不会再陌生.

创建队列

//创建一个队列,根据用户传入队列长度和消息节点大小来开辟相应的内存空间以供该队列使用,参数queueID带走队列ID
LITE_OS_SEC_TEXT_INIT UINT32 LOS_QueueCreate(CHAR *queueName, UINT16 len, UINT32 *queueID,UINT32 flags, UINT16 maxMsgSize)
{LosQueueCB *queueCB = NULL;UINT32 intSave;LOS_DL_LIST *unusedQueue = NULL;UINT8 *queue = NULL;UINT16 msgSize;(VOID)queueName;(VOID)flags;if (queueID == NULL) {return LOS_ERRNO_QUEUE_CREAT_PTR_NULL;}if (maxMsgSize > (OS_NULL_SHORT - sizeof(UINT32))) {// maxMsgSize上限 为啥要减去 sizeof(UINT32) ,因为前面存的是队列的大小return LOS_ERRNO_QUEUE_SIZE_TOO_BIG;}if ((len == 0) || (maxMsgSize == 0)) {return LOS_ERRNO_QUEUE_PARA_ISZERO;}msgSize = maxMsgSize + sizeof(UINT32);//总size = 消息体内容长度 + 消息大小(UINT32) /** Memory allocation is time-consuming, to shorten the time of disable interrupt,* move the memory allocation to here.*///内存分配非常耗时,为了缩短禁用中断的时间,将内存分配移到此处,用的时候分配队列内存queue = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, (UINT32)len * msgSize);//从系统内存池中分配,由这里提供读写队列的内存if (queue == NULL) {//这里是一次把队列要用到的所有最大内存都申请下来了,能保证不会出现后续使用过程中内存不够的问题出现return LOS_ERRNO_QUEUE_CREATE_NO_MEMORY;//调用处有 OsSwtmrInit sys_mbox_new DoMqueueCreate ==}SCHEDULER_LOCK(intSave);if (LOS_ListEmpty(&g_freeQueueList)) {//没有空余的队列ID的处理,注意软时钟定时器是由 g_swtmrCBArray统一管理的,里面有正在使用和可分配空闲的队列SCHEDULER_UNLOCK(intSave);//g_freeQueueList是管理可用于分配的队列链表,申请消息队列的ID需要向它要OsQueueCheckHook();(VOID)LOS_MemFree(m_aucSysMem1, queue);//没有就要释放 queue申请的内存return LOS_ERRNO_QUEUE_CB_UNAVAILABLE;}unusedQueue = LOS_DL_LIST_FIRST(&g_freeQueueList);//找到一个没有被使用的队列LOS_ListDelete(unusedQueue);//将自己从g_freeQueueList中摘除, unusedQueue只是个 LOS_DL_LIST 结点.queueCB = GET_QUEUE_LIST(unusedQueue);//通过unusedQueue找到整个消息队列(LosQueueCB)queueCB->queueLen = len;	//队列中消息的总个数,注意这个一旦创建是不能变的.queueCB->queueSize = msgSize;//消息节点的大小,注意这个一旦创建也是不能变的.queueCB->queueHandle = queue;	//队列句柄,队列内容存储区. queueCB->queueState = OS_QUEUE_INUSED;	//队列状态使用中queueCB->readWriteableCnt[OS_QUEUE_READ] = 0;//可读资源计数,OS_QUEUE_READ(0):可读.queueCB->readWriteableCnt[OS_QUEUE_WRITE] = len;//可些资源计数 OS_QUEUE_WRITE(1):可写, 默认len可写.queueCB->queueHead = 0;//队列头节点queueCB->queueTail = 0;//队列尾节点LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_READ]);//初始化可读队列链表LOS_ListInit(&queueCB->readWriteList[OS_QUEUE_WRITE]);//初始化可写队列链表LOS_ListInit(&queueCB->memList);//OsQueueDbgUpdateHook(queueCB->queueID, OsCurrTaskGet()->taskEntry);//在创建或删除队列调试信息时更新任务条目SCHEDULER_UNLOCK(intSave);*queueID = queueCB->queueID;//带走队列IDreturn LOS_OK;
}

解读

  • 创建和初始化一个LosQueueCB
  • 动态分配内存来保存消息内容,LOS_MemAlloc(m_aucSysMem1, (UINT32)len * msgSize);
  • msgSize = maxMsgSize + sizeof(UINT32);头四个字节放消息的长度,但消息最大长度不能超过maxMsgSize
  • readWriteableCnt记录读/写队列的数量,独立计算
  • readWriteList挂的是等待读取队列的任务链表 将在OsTaskWait(&queueCB->readWriteList[readWrite], timeout, TRUE);中将任务挂到链表上.

关键函数OsQueueOperate

队列的读写都要经过 OsQueueOperate

/************************************************
队列操作.是读是写由operateType定
本函数是消息队列最重要的一个函数,可以分析出读取消息过程中
发生的细节,涉及任务的唤醒和阻塞,阻塞链表任务的相互提醒.
************************************************/
UINT32 OsQueueOperate(UINT32 queueID, UINT32 operateType, VOID *bufferAddr, UINT32 *bufferSize, UINT32 timeout)
{LosQueueCB *queueCB = NULL;LosTaskCB *resumedTask = NULL;UINT32 ret;UINT32 readWrite = OS_QUEUE_READ_WRITE_GET(operateType);//获取读/写操作标识UINT32 intSave;SCHEDULER_LOCK(intSave);queueCB = (LosQueueCB *)GET_QUEUE_HANDLE(queueID);//获取对应的队列控制块ret = OsQueueOperateParamCheck(queueCB, queueID, operateType, bufferSize);//参数检查if (ret != LOS_OK) {goto QUEUE_END;}if (queueCB->readWriteableCnt[readWrite] == 0) {//根据readWriteableCnt判断队列是否有消息读/写if (timeout == LOS_NO_WAIT) {//不等待直接退出ret = OS_QUEUE_IS_READ(operateType) ? LOS_ERRNO_QUEUE_ISEMPTY : LOS_ERRNO_QUEUE_ISFULL;goto QUEUE_END;}if (!OsPreemptableInSched()) {//不支持抢占式调度ret = LOS_ERRNO_QUEUE_PEND_IN_LOCK;goto QUEUE_END;}//任务等待,这里很重要啊,将自己从就绪列表摘除,让出了CPU并发起了调度,并挂在readWriteList[readWrite]上,挂的都等待读/写消息的taskret = OsTaskWait(&queueCB->readWriteList[readWrite], timeout, TRUE);//任务被唤醒后会回到这里执行,什么时候会被唤醒?当然是有消息的时候!if (ret == LOS_ERRNO_TSK_TIMEOUT) {//唤醒后如果超时了,返回读/写消息失败ret = LOS_ERRNO_QUEUE_TIMEOUT;goto QUEUE_END;//}} else {queueCB->readWriteableCnt[readWrite]--;//对应队列中计数器--,说明一条消息只能被读/写一次}OsQueueBufferOperate(queueCB, operateType, bufferAddr, bufferSize);//发起读或写队列操作if (!LOS_ListEmpty(&queueCB->readWriteList[!readWrite])) {//如果还有任务在排着队等待读/写入消息(当时不能读/写的原因有可能当时队列满了==)resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&queueCB->readWriteList[!readWrite]));//取出要读/写消息的任务OsTaskWake(resumedTask);//唤醒任务去读/写消息啊SCHEDULER_UNLOCK(intSave);LOS_MpSchedule(OS_MP_CPU_ALL);//让所有CPU发出调度申请,因为很可能那个要读/写消息的队列是由其他CPU执行LOS_Schedule();//申请调度return LOS_OK;} else {queueCB->readWriteableCnt[!readWrite]++;//对应队列读/写中计数器++}QUEUE_END:SCHEDULER_UNLOCK(intSave);return ret;
}

解读

  • queueID 指定操作消息队列池中哪个消息队列
  • operateType 表示本次是是读/写消息
  • bufferAddrbufferSize表示如果读操作,用buf接走数据,如果写操作,将buf写入队列.
  • timeout只用于当队列中没有读/写内容时的等待.
    • 当读消息时发现队列中没有可读的消息,此时timeout决定是否将任务挂入等待读队列任务链表
    • 当写消息时发现队列中没有空间用于写的消息,此时timeout决定是否将任务挂入等待写队列任务链表
  • if (!LOS_ListEmpty(&queueCB->readWriteList[!readWrite]))最有意思的是这行代码.
    • 在一次读消息完成后会立即唤醒写队列任务链表的任务,因为读完了就有了剩余空间,等待写队列的任务往往是因为没有空间而进入等待状态.
    • 在一次写消息完成后会立即唤醒读队列任务链表的任务,因为写完了队列就有了新消息,等待读队列的任务往往是因为队列中没有消息而进入等待状态.

编程实例

创建一个队列,两个任务。任务1调用写队列接口发送消息,任务2通过读队列接口接收消息。

  • 通过LOS_TaskCreate创建任务1和任务2。
  • 通过LOS_QueueCreate创建一个消息队列。
  • 在任务1 send_Entry中发送消息。
  • 在任务2 recv_Entry中接收消息。
  • 通过LOS_QueueDelete删除队列。
#include "los_task.h"
#include "los_queue.h"static UINT32 g_queue;
#define BUFFER_LEN 50VOID send_Entry(VOID)
{UINT32 i = 0;UINT32 ret = 0;CHAR abuf[] = "test is message x";UINT32 len = sizeof(abuf);while (i < 5) {abuf[len -2] = '0' + i;i++;ret = LOS_QueueWriteCopy(g_queue, abuf, len, 0);if(ret != LOS_OK) {dprintf("send message failure, error: %x\n", ret);}LOS_TaskDelay(5);}
}VOID recv_Entry(VOID)
{UINT32 ret = 0;CHAR readBuf[BUFFER_LEN] = {0};UINT32 readLen = BUFFER_LEN;while (1) {ret = LOS_QueueReadCopy(g_queue, readBuf, &readLen, 0);if(ret != LOS_OK) {dprintf("recv message failure, error: %x\n", ret);break;}dprintf("recv message: %s\n", readBuf);LOS_TaskDelay(5);}while (LOS_OK != LOS_QueueDelete(g_queue)) {LOS_TaskDelay(1);}dprintf("delete the queue success!\n");
}UINT32 Example_CreateTask(VOID)
{UINT32 ret = 0; UINT32 task1, task2;TSK_INIT_PARAM_S initParam;initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)send_Entry;initParam.usTaskPrio = 9;initParam.uwStackSize = LOS_TASK_MIN_STACK_SIZE;initParam.pcName = "sendQueue";
#ifdef LOSCFG_KERNEL_SMPinitParam.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
#endifinitParam.uwResved = LOS_TASK_STATUS_DETACHED;LOS_TaskLock();ret = LOS_TaskCreate(&task1, &initParam);if(ret != LOS_OK) {dprintf("create task1 failed, error: %x\n", ret);return ret;}initParam.pcName = "recvQueue";initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)recv_Entry;ret = LOS_TaskCreate(&task2, &initParam);if(ret != LOS_OK) {dprintf("create task2 failed, error: %x\n", ret);return ret;}ret = LOS_QueueCreate("queue", 5, &g_queue, 0, BUFFER_LEN);if(ret != LOS_OK) {dprintf("create queue failure, error: %x\n", ret);}dprintf("create the queue success!\n");LOS_TaskUnlock();return ret;
}

结果验证

create the queue success!
recv message: test is message 0
recv message: test is message 1
recv message: test is message 2
recv message: test is message 3
recv message: test is message 4
recv message failure, error: 200061d
delete the queue success!

总结

  • 消息队列解决任务间大数据的传递
  • 以一种异步,解耦的方式实现任务通讯
  • 全局由消息队列池统一管理
  • 在创建消息队列时申请内存块存储消息内存.
  • 读/写操作统一管理,分开执行,A任务 读/写完消息后会立即唤醒等待写/读的B任务.

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

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

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

相关文章

Discourse 清理存储空间的方法

Discourse 使用一段时间以后会发现硬盘空间占用非常多。 主要是因为 Docker Image 的问题&#xff0c;如果升级次数越多&#xff0c;空间占用越多。 运行下面的命令&#xff1a; ./launcher cleanup 能够帮助你清理 Discourse 占用的空间。 如下面代码所示&#xff1a; […

牛客热题:单链表排序

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;单链表排序题目链接方法一&…

Windows php 安装 Memcached扩展、php缺失 Memcached扩展、Class ‘Memcached‘ not found

在Windows系统下如何安装 php Memcached 扩展 下载dll文件 pecl地址&#xff1a;https://pecl.php.net/package/memcached 根据版本进行选择 &#xff1a; 解压下载的文件后得到了这么样的文件结构&#xff1a; 配置 移动dll文件到相应文件位置 重点&#xff1a; libme…

jdk环境安装

jdk安装 创建软件安装的目录 mkdir -p /bigdata/{soft,server} /bigdata/soft 安装文件的存放目录 /bigdata/server 软件安装的目录 把安装的软件上传到/bigdata/soft 目录 解压到指定目录 -C :指定解压到指定目录 tar -zxvf /bigdata/soft/jdk-8u241-linux-x64.tar.gz -C /b…

【Osek网络管理测试】[TG3_TC3]tSleepRequestMin_L

&#x1f64b;‍♂️ 【Osek网络管理测试】系列&#x1f481;‍♂️点击跳转 文章目录 1.环境搭建2.测试目的3.测试步骤4.预期结果5.测试结果 1.环境搭建 硬件&#xff1a;VN1630 软件&#xff1a;CANoe 2.测试目的 验证DUT进入NMLimpHome状态后请求睡眠的最短时间是否正确…

Android --- 消息机制与异步任务

在Android中&#xff0c;只有在UIThread(主线程)中才能直接更新界面&#xff0c; 在Android中&#xff0c;长时间的工作联网都需要在workThread(分线程)中执行 在分线程中获取服务器数据后&#xff0c;需要立即到主线程中去更新UI来显示数据&#xff0c; 所以&#xff0c;如…

NI CRIO 9045 LABVIEW2020

1.labview工程如果要访问CRIO&#xff0c;需要设置以下&#xff0c;否则在项目中连接失败。 2.项目中如果要传文件&#xff0c;需要安装WebDEV 3.使用WebDAV将文件传输到实时(RT)目标 https://knowledge.ni.com/KnowledgeArticleDetails?idkA03q000000YGytCAG&lzh-CN

Mars3d实现用一个button控制一个map.control的显示与隐藏

原生js,想做一个button,控制比如compass的显示与隐藏 点一下显示 再次单击的时候就隐藏掉 写了一个function控制显示隐藏 function addCompass(){ if(compass.showtrue) { compass.showfalse; } else{ compass.showtrue; } } 功能示例(Vue版) | Mars3D三维可视化平台 | 火星…

深入了解C/C++的内存区域划分

&#x1f525;个人主页&#xff1a;北辰水墨 &#x1f525;专栏&#xff1a;C学习仓 本节我们来讲解C/C的内存区域划分&#xff0c;文末会附加一道题目来检验成果&#xff08;有参考答案&#xff09; 一、大体有哪些区域&#xff1f;分别存放什么变量开辟的空间&#xff1f; …

【微服务】网关(详细知识以及登录验证)

微服务网关 网关网关路由快速入门路由属性 路由断言网关登录校验自定义过滤器实现登录校验网关传递用户OpenFeign传递用户 网关 网络的关口&#xff0c;负责请求的路由&#xff0c;转发&#xff0c;身份校验 当我们把一个单体项目分成多个微服务并部署在多台服务器中&#xff…

Redis__数据类型

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a;Redis__数据类型 ⏱️ 创作时间&#xff1a;2024年04月28日 ———————————————— 这里写目录标题 文…

大模型争霸的下一站:不仅是超越GPT-4,更是寻求模型之间的平衡应用

文 | 智能相对论 作者 | 沈浪 知名科学杂志《Nature》发表了一篇关于大模型规模参数大小争议的文章《In Al, is bigger always better?》——AI大模型&#xff0c;越大越好吗&#xff1f;随着大模型应用走向实践&#xff0c;这一问题不可避免地成为了当前AI行业发展的焦点与…

迅雷永久破解

链接&#xff1a;https://pan.baidu.com/s/1ZGb1ljTPPG3NFsI8ghhWbA?pwdok7s 下载后解压 以管理员身份运行绿化.bat&#xff0c;会自动生成快捷方式&#xff0c;如果没有可以在program中运行Thunder.exe

【python】条件语句与循环语句

目录 一.条件语句 1.定义 2.条件语句格式 &#xff08;1&#xff09;if &#xff08;2&#xff09;if-else &#xff08;3&#xff09;elif功能 &#xff08;4&#xff09;if嵌套使用 3.猜拳游戏 二.循环语句 1. while循环 2.while嵌套 3.for循环 4.break和conti…

AI图书推荐:AI在语言学习教育领域的应用和挑战

这本书《AI在语言学习教育领域的应用和挑战》&#xff08;AI in Language Teaching, Learning, and Assessment&#xff09;由Fang Pan编辑&#xff0c;出版于IGI Global&#xff0c;主要探讨了人工智能&#xff08;AI&#xff09;在语言教育领域的应用、挑战以及潜在的益处。 …

JS基础:JS语法规范详解(最全!)

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端基础路线”&#xff0c;可获取完整web基础…

C++:自增运算符(++)重载

自增运算符&#xff08;&#xff09;分为前置自增和后置自增&#xff0c;它们两者主要的区别是&#xff1a;返回的值不同&#xff0c;以及执行自增操作的顺序不同。 前置自增运算符 &#xff1a; 前置自增运算符首先将操作数加1&#xff0c;然后返回自增后的值。 这意味着如果…

CNN笔记详解

CNN(卷积神经网络) 计算机视觉&#xff0c;当你们听到这一概念的是否好奇计算机到底是怎样知道这个图片是什么的呢&#xff1f;为此提出了卷积神经网络&#xff0c;通过卷积神经网络&#xff0c;计算机就可以识别出图片中的特征&#xff0c;从而识别出图片中的物体。看到这里充…

盘一盘接口测试的那些痛点,你现在会解决了吗

前言 说到接口测试&#xff0c;想必大家一定不会陌生。接口测试就是测试系统组件间&#xff0c;接口对接是否顺畅的一种测试。包括测试数据能否交换、能否传递、能否正常控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系&#xff0c;等等。 由于接口测试主要是检测系统…

2024网络安全面试问题宝典(4万字)

2024网络安全厂商面试问题宝典(4万字) 目录 评分标准网络基础问题 TCP建立连接要进行3次握手&#xff08;syn-syn&#xff0c;ack-ack&#xff09;&#xff0c;而断开连接要进行4次&#xff08;fin-ack-fin-ack&#xff09;TCP&#xff0c;UDP区别&#xff1a;安全常用的协议…