鸿蒙内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了

内核中哪些地方会用到互斥锁?看图:

图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能.

概述

自旋锁互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争.

自旋锁的特点是死守共享资源,拿不到锁,CPU选择睡眠,等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克.

互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题:

第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表.

第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式.

第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办?
答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被还原.所以一个任务的优先级会看情况时高时低.

第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级.

带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明.

互斥锁长什么样?

enum {
    LOS_MUX_PRIO_NONE = 0,  //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队.
    LOS_MUX_PRIO_INHERIT = 1, //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。
           //当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级
    LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义
};
enum {
    LOS_MUX_NORMAL = 0,  //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测)
    LOS_MUX_RECURSIVE = 1, //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。
    LOS_MUX_ERRORCHECK = 2, //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。
    LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁
};
typedef struct { //互斥锁的属性
    UINT8 protocol;  //协议
    UINT8 prioceiling; //优先级上限
    UINT8 type;   //类型属性
    UINT8 reserved;  //保留字段
} LosMuxAttr;

typedef struct OsMux { //互斥锁结构体
    UINT32 magic;        /**< magic number */  //魔法数字
    LosMuxAttr attr;     /**< Mutex attribute */ //互斥锁属性
    LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上
    LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别.
    VOID *owner;         /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务
    UINT16 muxCount;     /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次
} LosMux;

初始化

LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr)
{   //...
    SCHEDULER_LOCK(intSave);        //拿到调度自旋锁
    mutex->muxCount = 0;            //锁定互斥量的次数
    mutex->owner = NULL;            //持有该锁的任务
    LOS_ListInit(&mutex->muxList);  //初始化等待该锁的任务链表
    mutex->magic = OS_MUX_MAGIC;    //固定标识,互斥锁的魔法数字
    SCHEDULER_UNLOCK(intSave);      //释放调度自旋锁
    return LOS_OK;
}

留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务.

三种申请模式

申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。

无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。

永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。

定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。

如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。
如果没有任务阻塞于该互斥锁,则互斥锁释放成功。

申请互斥锁主函数 OsMuxPendOp

//互斥锁的主体函数,由OsMuxlockUnsafe调用,互斥锁模块最重要的几个函数之一
//最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行. 
STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux *mutex, UINT32 timeout)
{
    UINT32 ret;
    LOS_DL_LIST *node = NULL;
    LosTaskCB *owner = NULL;

    if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {//列表为空时的处理
        /* This is for mutex macro initialization. */
        mutex->muxCount = 0;//锁计数器清0
        mutex->owner = NULL;//锁没有归属任务
        LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去
    }

    if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回
        mutex->muxCount++;              //互斥锁计数器加1
        mutex->owner = (VOID *)runTask; //当前任务拿到锁
        LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);//持有锁的任务改变了,节点挂到当前task的锁链表
        if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {//看保护协议的做法是怎样的?
            LOS_BitmapSet(&runTask->priBitMap, runTask->priority);//1.priBitMap是记录任务优先级变化的位图,这里把任务当前的优先级记录在priBitMap
            OsTaskPriModify(runTask, mutex->attr.prioceiling);//2.把高优先级的mutex->attr.prioceiling设为当前任务的优先级.
        }//注意任务优先级有32个, 是0最高,31最低!!!这里等于提高了任务的优先级,目的是让其在下次调度中继续提高被选中的概率,从而快速的释放锁.
        return LOS_OK;
    }
    //递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了
    if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//第一种情况 runtask是锁持有方
        mutex->muxCount++;  //递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE)
        return LOS_OK;      //成功退出
    }
    //到了这里说明锁在别的任务那里,当前任务只能被阻塞了.
    if (!timeout) {//参数timeout表示等待多久再来拿锁
        return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock 
    }
    //自己要被阻塞,只能申请调度,让出CPU core 让别的任务上
    if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁)
        return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有
    }

    OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);//设置锁位图,尽可能的提高锁持有任务的优先级

    owner = (LosTaskCB *)mutex->owner;  //记录持有锁的任务
    runTask->taskMux = (VOID *)mutex;   //记下当前任务在等待这把锁
    node = OsMuxPendFindPos(runTask, mutex);//在等锁链表中找到一个优先级比当前任务更低的任务
    ret = OsTaskWait(node, timeout, TRUE);//task陷入等待状态 TRUE代表需要调度
    if (ret == LOS_ERRNO_TSK_TIMEOUT) {//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文
        runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了
        ret = LOS_ETIMEDOUT;//返回超时
    }

    if (timeout != LOS_WAIT_FOREVER) {//不是永远等待的情况
        OsMuxBitmapRestore(mutex, runTask, owner);//恢复锁的位图
    }

    return ret;
}

释放锁的主体函数 OsMuxPostOp

//是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的
//OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由OsMuxPostOp来回答这个问题
STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched)
{
    LosTaskCB *resumedTask = NULL;

    if (LOS_ListEmpty(&mutex->muxList)) {//如果互斥锁列表为空
        LOS_ListDelete(&mutex->holdList);//把持有互斥锁的节点摘掉
        mutex->owner = NULL;
        return LOS_OK;
    }

    resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));//拿到等待互斥锁链表的第一个任务实体,接下来要唤醒任务
    if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {//互斥锁属性协议是继承会怎么操作?
        if (resumedTask->priority > taskCB->priority) {//拿到锁的任务优先级低于参数任务优先级
            if (LOS_HighBitGet(taskCB->priBitMap) != resumedTask->priority) {//参数任务bitmap中最低的优先级不等于等待锁的任务优先级
                LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);//把等待任务锁的任务的优先级记录在参数任务的bitmap中
            }
        } else if (taskCB->priBitMap != 0) {//如果bitmap不等于0说明参数任务至少有任务调度的优先级
            OsMuxPostOpSub(taskCB, mutex);//
        }
    }
    mutex->muxCount = 1;//互斥锁数量为1
    mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了
    resumedTask->taskMux = NULL;//resumedTask不再等锁了
    LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去
    LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录
    OsTaskWake(resumedTask);//resumedTask有了锁就唤醒它,因为当初在没有拿到锁时处于了pend状态
    if (needSched != NULL) {//如果不为空
        *needSched = TRUE;//就走起再次调度流程
    }

    return LOS_OK;
}

编程实例

本实例实现如下流程。

  • 任务Example_TaskEntry创建一个互斥锁,锁任务调度,创建两个任务Example_MutexTask1、Example_MutexTask2。Example_MutexTask2优先级高于Example_MutexTask1,解锁任务调度,然后Example_TaskEntry任务休眠300Tick。

  • Example_MutexTask2被调度,以永久阻塞模式申请互斥锁,并成功获取到该互斥锁,然后任务休眠100Tick,Example_MutexTask2挂起,Example_MutexTask1被唤醒。

  • Example_MutexTask1以定时阻塞模式申请互斥锁,等待时间为10Tick,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。10Tick超时时间到达后,Example_MutexTask1被唤醒,以永久阻塞模式申请互斥锁,因互斥锁仍被Example_MutexTask2持有,Example_MutexTask1挂起。

  • 100Tick休眠时间到达后,Example_MutexTask2被唤醒, 释放互斥锁,唤醒Example_MutexTask1。Example_MutexTask1成功获取到互斥锁后,释放锁。

  • 300Tick休眠时间到达后,任务Example_TaskEntry被调度运行,删除互斥锁,删除两个任务。

/* 互斥锁句柄id */
UINT32 g_testMux;
/* 任务ID */
UINT32 g_testTaskId01;
UINT32 g_testTaskId02;

VOID Example_MutexTask1(VOID)
{
    UINT32 ret;

    printf("task1 try to get  mutex, wait 10 ticks.\n");
    /* 申请互斥锁 */
    ret = LOS_MuxPend(g_testMux, 10);

    if (ret == LOS_OK) {
        printf("task1 get mutex g_testMux.\n");
        /* 释放互斥锁 */
        LOS_MuxPost(g_testMux);
        return;
    } else if (ret == LOS_ERRNO_MUX_TIMEOUT ) {
            printf("task1 timeout and try to get mutex, wait forever.\n");
            /* 申请互斥锁 */
            ret = LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);
            if (ret == LOS_OK) {
                printf("task1 wait forever, get mutex g_testMux.\n");
                /* 释放互斥锁 */
                LOS_MuxPost(g_testMux);
                return;
            }
    }
    return;
}

VOID Example_MutexTask2(VOID)
{
    printf("task2 try to get  mutex, wait forever.\n");
    /* 申请互斥锁 */
    (VOID)LOS_MuxPend(g_testMux, LOS_WAIT_FOREVER);

    printf("task2 get mutex g_testMux and suspend 100 ticks.\n");

    /* 任务休眠100Ticks */
    LOS_TaskDelay(100);

    printf("task2 resumed and post the g_testMux\n");
    /* 释放互斥锁 */
    LOS_MuxPost(g_testMux);
    return;
}

UINT32 Example_TaskEntry(VOID)
{
    UINT32 ret;
    TSK_INIT_PARAM_S task1;
    TSK_INIT_PARAM_S task2;

    /* 创建互斥锁 */
    LOS_MuxCreate(&g_testMux);

    /* 锁任务调度 */
    LOS_TaskLock();

    /* 创建任务1 */
    memset(&task1, 0, sizeof(TSK_INIT_PARAM_S));
    task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask1;
    task1.pcName       = "MutexTsk1";
    task1.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task1.usTaskPrio   = 5;
    ret = LOS_TaskCreate(&g_testTaskId01, &task1);
    if (ret != LOS_OK) {
        printf("task1 create failed.\n");
        return LOS_NOK;
    }

    /* 创建任务2 */
    memset(&task2, 0, sizeof(TSK_INIT_PARAM_S));
    task2.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_MutexTask2;
    task2.pcName       = "MutexTsk2";
    task2.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    task2.usTaskPrio   = 4;
    ret = LOS_TaskCreate(&g_testTaskId02, &task2);
    if (ret != LOS_OK) {
        printf("task2 create failed.\n");
        return LOS_NOK;
    }

    /* 解锁任务调度 */
    LOS_TaskUnlock();
    /* 休眠300Ticks */
    LOS_TaskDelay(300);

    /* 删除互斥锁 */
    LOS_MuxDelete(g_testMux);

    /* 删除任务1 */
    ret = LOS_TaskDelete(g_testTaskId01);
    if (ret != LOS_OK) {
        printf("task1 delete failed .\n");
        return LOS_NOK;
    }
    /* 删除任务2 */
    ret = LOS_TaskDelete(g_testTaskId02);
    if (ret != LOS_OK) {
        printf("task2 delete failed .\n");
        return LOS_NOK;
    }

    return LOS_OK;
}

结果验证

task2  to get  mutex wait forever
task2 get mutex g_testMux  suspend  ticks
task1  to get  mutex wait  ticks
task1 timeout   to get mutex wait forever
task2 resumed  post the g_testMux
task1 wait foreverget mutex g_testMux

总结

1.互斥锁解决的是任务间竞争共享内存的问题.

2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁.

3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务.

4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数.

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

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Revit模型移动设备加载优化

BIM/CAD 模型可能包含大量细节&#xff0c;在智能手机和移动 VR 设备上加载时需要特别注意。以下是保持Revit模型整洁的一些步骤&#xff0c;以便任何人都可以毫无问题地加载它们。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 -…

亏了亏了!双向孟德尔随机化阴性结果居然发了SCI二区(IF=6.7)

‍ 今天为诸位介绍的这篇文章是一项双向孟德尔随机化研究&#xff08;MR&#xff09;&#xff0c;惊讶的是&#xff0c;双向因果均为阴性结果发了SCI二区&#xff01;我们一起来看看&#xff01; 2024年4月17日&#xff0c;广东医科大学附属医院的学者做了一项双向两样本孟德尔…

绝地求生:PUBG杜卡迪联名进入倒计时3天!

大家好&#xff0c;我是闲游盒。 杜卡迪联名已经进入倒计时3天&#xff01;喜欢的朋友要注意结束时间可千万别错过&#xff01; 杜卡迪6色车辆 随着五一小长假的结束&#xff0c;本次混沌漫彩通行证也即将结束&#xff0c;本次通行证31级之后没升1级可额外领取1500BP和挑战者纪…

Spring与Mybatis-增删改查(注解方式与配置文件方式)

Spring框架下Mybaits的使用 准备数据库配置application.propertiespom.xml添加lombok依赖创建Emp实体类准备Mapper接口&#xff1a;EmpMapper预编译SQL根据id查询数据Mapper接口方法配置application.properties开启自动结果映射单元测试 条件模糊查询Mapper接口方法单元测试 根…

大模型时序预测初步调研20240506

AI预测相关目录 AI预测流程&#xff0c;包括ETL、算法策略、算法模型、模型评估、可视化等相关内容 最好有基础的python算法预测经验 EEMD策略及踩坑VMD-CNN-LSTM时序预测对双向LSTM等模型添加自注意力机制K折叠交叉验证optuna超参数优化框架多任务学习-模型融合策略Transform…

目标检测常用评价指标详解

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

数字孪生—物联网技术

数字孪生涉及到诸多技术领域&#xff0c;物联网技术在数据孪生项目中具有重要的应用价值&#xff0c;主要体现在以下几个方面&#xff1a; 1.数据采集和实时监测&#xff1a;物联网技术可以用于实时采集各种设备、传感器和设施的数据&#xff0c;包括温度、湿度、压力、振动等…

CMakeLists.txt语法规则:foreach循环的关键字

一. 简介 前一篇文章学习了 CMakeLists.txt语法中 foreach循环的基本用法。文章如下&#xff1a; CMakeLists.txt语法规则&#xff1a;foreach 循环基本用法-CSDN博客 本文继续 CMakeLists.txt语法中 foreach循环语句&#xff0c;主要学习 foreach循环中的关键字。 二. CM…

【OceanBase诊断调优】—— SQL 执行报错而不能计入 SQL_AUDIT 的情况

通常&#xff0c;执行成果的 SQL 都会计入 SQL_AUDIT 中&#xff0c;而执行报错的 SQL 则需要依据其执行报错的阶段来决定是否计入 SQL_AUDIT 中。 在 OceanBase 数据库中&#xff0c;SQL 请求的执行流程如图所示。 如果 SQL 在进入 Executor 阶段前发生报错&#xff0c;则该 …

java-Spring-Lombok-讲解-(一文一言)创伤是成熟的途径

高手都在孤独前进-致敬我们不悔的青春 我打算每篇文章下找一下文言警句-说不那天会用上&#x1f601;&#x1f601;&#x1f601;&#x1f601; 每篇一言 创伤是成熟的途径 希望经历过创伤的人,能更好享受当下, 爱自己胜过爱别人呀 目录 &#x1f3bb;Lombok简介 &#…

KIE关键信息抽取——SDMG-R

https://arxiv.org/pdf/2103.14470https://arxiv.org/pdf/2103.14470 1.概述 背景:传统的关键信息提取方法依赖于模板匹配,这使它们难以泛化到未见过的模板,且对文本识别错误不够鲁棒。SDMG-R方法:提出一种端到端的双模态图推理方法,通过构建双模态图(视觉和文本特征),…

Celery(分布式任务队列)入门学习笔记

Celery 的简单介绍 用 Celery 官方的介绍&#xff1a;它是一个分布式任务队列; 简单&#xff0c;灵活&#xff0c;可靠的处理大量消息的分布式系统; 它专注于实时处理&#xff0c;并支持任务调度。 Celery 如果使用 RabbitMQ 作为消息系统的话&#xff0c;整个应用体系就是下…

GStreamer日志调试笔记

1、查询所有分类 #gst-launch-1.0 --gst-debug-help 2、查询videotestsrc的日志 #gst-launch-1.0 --gst-debug-help | findstr videotestsrc 结果&#xff1a; 3、使用--gst-debug设置相应日志类型的相应等级&#xff0c;越大显示日志越多&#xff0c;排查内存泄露可以设置为9 …

【iOS】多线程

文章目录 前言一、多线程的选择方案二、GCD和NSOperation的比较二、多线程相关概念任务队列 三、死锁情况主队列加同步任务 四、任务队列组合主队列异步并发队列异步 前言 这两天将iOS的多线程的使用都看了一遍&#xff0c;iOS的多线程方案有许多&#xff0c;本篇博客主要总结…

打开深度学习的锁:(0)什么是神经网络?

PS&#xff1a;每每温故必而知新 什么是神经网络&#xff1f; 一、一个单神经元的神经网络二、多个单神经元的神经网络三、到底什么是机器学习&#xff1f;&#xff08;重点&#xff09;1&#xff1a;什么是机器学习的训练&#xff1f;2&#xff1a;什么是模型&#xff1f;权重…

python数据分析所需要的语法基础

Python语言基础——语法基础 前言语法基础变量标识符数据类型输入与输出代码缩进与注释 总结 前言 对于学过C语言的人来说&#xff0c;python其实很简单。学过一种语言&#xff0c;学习另一种语言&#xff0c;很显然的能感觉到&#xff0c;语言大体上都是相通的。当然&#xf…

【docker】常用的把springboot打包为docker镜像的maven插件

Spring Boot Maven Plugin: Spring Boot 自带的 Maven 插件 (spring-boot-maven-plugin) 支持直接生成 Docker 镜像。通过配置&#xff0c;可以在 Maven 构建过程中自动构建 Docker 镜像&#xff0c;而无需单独编写 Dockerfile。这种方法简化了将应用打包为 Docker 镜像的过程。…

武汉理工大学python123实验——流程控制结构

1.百分制成绩转换五分制#1707 n int(input())if n>90:print(A) elif n>80:print(B) elif n>70:print(C) elif n>60:print(D) else:print(E) 2.角古猜想#73963 n eval(input()) if n<0:print(ERROR) elif . in str(n):print(ERROR) else:print(n,end" …

Qt扫盲-Qt D-Bus概述

Qt D-Bus概述 一、概述二、总线三、相关概念1. 消息2. 服务名称3. 对象的路径4. 接口5. 备忘单 四、调试五、使用Qt D-Bus 适配器1. 在 D-Bus 适配器中声明槽函数1. 异步槽2. 只输入槽3. 输入输出槽4. 自动回复5. 延迟回复 一、概述 D-Bus是一种进程间通信(IPC)和远程过程调用…

Java面试题:多线程2

如何停止正在运行的线程 1,使用退出标志,使线程正常退出(run方法中循环对退出标志进行判断) 2,使用stop()方法强行终止(不推荐) 3,调用interrupt()方法中断线程 打断阻塞线程(sleep,wait,join),线程会抛出InterruptedException异常 打断正常的线程,可以根据打断状态来标记…