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

news2024/11/15 2:07:21

基本概念

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

  • 任务能够从队列里面读取消息,当队列中的消息为空时,挂起读取任务;当队列中有新消息时,挂起的读取任务被唤醒并处理新消息。任务也能够往队列里写入消息,当队列已经写满消息时,挂起写入任务;当队列中有空闲消息节点时,挂起的写入任务被唤醒并写入消息。如果将读队列和写队列的超时时间设置为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 */			//队列ID
    UINT16 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);//清0
    LOS_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 来找到 LosQueueCB

    if (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;//带走队列ID
    return 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]上,挂的都等待读/写消息的task
        ret = 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 50

VOID 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_SMP
    initParam.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());
#endif
    initParam.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.coloradmin.cn/o/1645916.html

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

相关文章

408数据结构-树与森林 自学知识点整理

前置知识&#xff1a;树的基本概念与性质 树的存储结构 树既可以采用顺序存储结构&#xff0c;又可采用链式存储结构。但无论采取哪种方式&#xff0c;都要求能够唯一地反映树中各结点之间的逻辑关系。 1. 双亲表示法 这种存储结构采用一组连续空间来存储每个结点&#xff0…

模拟电路设计与分析——电流

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 原子电流的产生电流的单位电流的方向直流电交流电直流&交流对比开路&闭路 原子 世界万物都是由原子构成&#xff0c;那么…

Pytorch实现图片异常检测

图片异常检测 异常检测指的是在正常的图片中找到异常的数据&#xff0c;由于无法通过规则进行识别判断&#xff0c;这样的应用场景通常都是需要人工进行识别&#xff0c;比如残次品的识别&#xff0c;图片异常识别模型的目标是可以代替或者辅助人工进行识别异常图片。 AnoGAN…

P8800 [蓝桥杯 2022 国 B] 卡牌

P8800 [蓝桥杯 2022 国 B] 卡牌 分析 “最多” -- 二分 1.二分区间&#xff08;凑齐的卡牌套数&#xff09;&#xff1a; l&#xff1a;a[]min&#xff1b;r&#xff1a;(a[]b[])max 2.check(x)&#xff1a; &#xff08;1&#xff09;for循环内&#xff1a; 判断x - a[i…

ComfyUI 基础教程(十三):ComfyUI-Impact-Pack 面部修复

SD的WebUI 中的面部修复神器 ADetailer,无法在ComfyUI 中使用。那么如何在ComfyUI中进行面部处理呢?ComfyUI 中也有几个面部修复功能,比如ComfyUI Impact Pack(FaceDetailer),以及换脸插件Reactor和IPAdapter。 ComfyUI-Impact-Pack 是一个功能强大的插件,专为 ComfyUI …

矩池云jupyter运行opengait代码 未完成版

文章目录 前言——矩池云的使用技巧1.切换源 一、下载数据集二、下载模型三、环境配置1.查看python、torch、torchvision版本2.查看一些包版本是否过高3.下载包 四、开始训练1.设置环境变量2.遇到的问题&#xff08;1&#xff09;torch.cuda.is_available()返回false&#xff0…

《第一行代码》第二版学习笔记(8)——网络技术

文章目录 一、Http1、HttpURLConnection2、OKHttp 二、解析JSON格式数据1、使用JSONObject2、使用GSON解析JSON数据 一、Http 1、HttpURLConnection public void run() {HttpURLConnection connection null;BufferedReader reader null;try {URL url new URL("http://…

springboot版本升级,及解决springsecurity漏洞问题

背景&#xff1a; 项目中要解决 Spring Security RegexRequestMatcher 认证绕过漏洞&#xff08;CVE-2022-22978&#xff09; 漏洞问题&#xff0c;并且需要将项目的版本整体升级到boot版本2.1.7&#xff0c;升级改造过程非常的痛苦&#xff0c;一方面对整个框架的代码不是很熟…

嵌入式复习重点

嵌入式系统有多种表现形式&#xff0c;包括计算机MCU、SOC片上系统、SOPC片上系统、GPU和FPGA等。 MCU(微控制器): 是最基本也是最常见的嵌入式系统形式,是集成了CPU、ROM、RAM、IO口、定时器、中断控制器等组件的单一芯片。MCU广泛用于电器电子产品的控制。SoC(系统片上芯片):…

vivado UltraScale 比特流设置

下表所示 UltraScale ™ 器件的器件配置设置可搭配 set_property <Setting> <Value> [current_design] Vivado 工具 Tcl 命令一起使用。

地下管线管网三维参数化建模软件MagicPipe3D V3.5

经纬管网建模系统MagicPipe3D&#xff08;www.magic3d.net&#xff09;自主安全可控&#xff0c;本地离线参数化构建三维管网模型&#xff08;管道、接头、附属物等&#xff09;&#xff0c;输出标准3DTiles、Obj等格式&#xff0c;支持Cesium、Unreal、Unity等引擎可视化查询分…

09_电子设计教程基础篇(电阻)

文章目录 前言一、电阻原理二、电阻种类1.固定电阻1、材料工艺1、线绕电阻2、非线绕电阻1、实心电阻1、有机实心电阻2、无机实心电阻 2、薄膜电阻&#xff08;常用&#xff09;1、碳膜电阻2、合成碳膜电阻3、金属膜电阻4、金属氧化膜电阻5、玻璃釉膜电阻 3、厚膜电阻&#xff0…

Jenkins 2.164.3 安装插件(当前官网正式版本: 2.440.3 LTS)

Jenkins 2.164.3安装插件 1. 安装jenkins1.1 宿主机安装1.2 docker安装(linux) 2. 登录jenkins3. 修改配置文件 这篇文章如果放在5、6年前写出来毫无意义&#xff0c;因为安装2.164.3之后&#xff0c;推荐的插件即可自动安装。但是在2024年&#xff0c;当前正式版本是2.440.3 L…

武汉星起航:跨境电商行业领航者,一站式孵化服务引领全球趋势

在全球化日益深入的今天&#xff0c;跨境电商作为连接各国市场的桥梁&#xff0c;其重要性日益凸显。在这一潮流中&#xff0c;武汉星起航电子商务有限公司以其前瞻性的战略眼光和丰富的运营经验&#xff0c;迅速崛起为跨境电商行业的领军者。公司不仅自营亚马逊跨境电商业务&a…

echarts柱状图实现左右横向对比

实现效果如上图 其实是两组数据&#xff0c;其中一组数据改为负数&#xff0c;然后 在展示的时候&#xff0c;在将负数取反 第一处修改坐标轴 xAxis: [{type: value,axisLabel: {formatter: function (value) {if (value < 0) {return -value;}else{return value;}}}}], 第…

书客大路灯Sun自掀起护眼新浪潮以来,纷纷直呼“真香现场”

随着科技时代与人们生活水平需求的日益增长&#xff0c;加上网课学习和居家办公的普及&#xff0c;各大灯具厂商都开始搭建起自家的生态&#xff0c;尤其是大路灯这个以外不被重视的品类&#xff0c;也开始找到了自身的定位与价值。 尤其在2024年上半年发布的——SUKER书客护眼…

截取字符串的3种方法

一、截取字符串的实现 在C语言中&#xff0c;没有直接截取字符串的库函数&#xff0c;但是咱们可以借助其他函数实现这个功能。 1&#xff0e;最简单的方法 如果只是直接输出一个字符串的子串&#xff0c;只需要一个简单的printf函数即可。 #include <stdio.h> int m…

MySQL之查询 拿下 * 。*

DQL数据查询语言 对上述的的查询操作进行代码演示&#xff08;续上一篇学生表代码进行处理&#xff09; 下面是上一篇的代码分享 下面进行简单的查询操作 字符串如果强行进行算数运算默认只为0 查询时常用的单行函数列举 未完待续

【机器学习】必会算法之:AdaBoost

AdaBoost 1、引言2、AdaBoost2.1 定义2.2 优缺点2.2.1 优点2.2.2 缺点 2.3 实现方式2.4 算法公式2.5 代码示例 3、总结 1、引言 小屌丝&#xff1a;鱼哥&#xff0c; 这五一要去哪里浪啊&#xff1f; 小鱼&#xff1a;哪也不去&#xff1f; 小屌丝&#xff1a;&#xff1f;&am…

[激光原理与应用-92]:振镜的光路图原理

目录 一、振镜的光路 二、振镜的工作原理 2.1 概述 2.2 焊接头 2.3 准直聚焦头-直吹头 2.4 准直聚焦头分类——按应用分 2.4.1 准直聚焦头分类——功能分类 2.4.2 准直聚焦头镜片 2.4.3 振镜焊接头 2.4.4 振镜分类&#xff1a; 2.4.5 动态聚焦系统演示&#xff08;素…