【RTOS学习】事件组 | 任务通知
🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
事件组 | 任务通知
- 🚁事件组
- 🛴大概原理
- 🛴使用事件组的函数
- 同步点
- 🛴基本使用
- 🚁任务通知
- 🛴大概原理
- 🛴使用任务通知的函数
- 🛴基本使用
- 🚁总结
🚁事件组
事件组也是一种实现同步与互斥的方法,可以简单地认为就是一个整数,每一位表示一个事件,每一位事件的含义由程序员决定,比如:Bit0 表示用来串口是否就绪,Bit1 表示按键是否被按下。
这些位,值为 1 表示事件发生了,值为 0 表示事件没发生,一个或多个任务、都可以去写这些位;一个或多个任务、都可以去读这些位。
- 可以等待某一位、某些位中的任意一个,也可以等待多位。
如上图所示是事件组的模型,左边的任务A和任务B是生产者任务,任务A完成事件后会将事件组中的bit0置为1,任务B完成事件后会将bit2置为1。
任务C和任务D是消费者任务,它们读取事件组的每一位,任务C和任务D都可以指定等待哪一位置一,当检测到bit0或bit2为1时,说明对应的事件完成了,任务C和任务D就可以进行下一步操作。
事件组用一个整数来表示,其中的高 8 位留给内核使用,只能用其他的位来表示事件:
- 如果 configUSE_16_BIT_TICKS 是 1,那么这个整数就是 16 位的,低 8 位用来表示事件。
- 如果 configUSE_16_BIT_TICKS 是 0,那么这个整数就是 32 位的,低 24 位用来表示事件 。
configUSE_16_BIT_TICKS
是用来表示 Tick Count
的,怎么会影响事件组?这只是基于效率来考虑 :
- 如果 configUSE_16_BIT_TICKS 是 1,就表示该处理器使用 16 位更高效,所以事件组也使用 16 位。
- 如果 configUSE_16_BIT_TICKS 是 0,就表示该处理器使用 32 位更高效,所以事件组也使用 32 位 。
事件组和队列、信号量等不太一样,主要集中在 2 个地方:
-
事件发生后要唤醒哪个等待的任务?
-
队列、信号量:事件发生时,一次只会唤醒一个任务。
-
事件组:事件发生时,会唤醒所有符合条件的任务,简单地说它有"广播"的作用 。
以上图为列,任务 C、D 等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位。
简单地说就是"或"、"与"的关系。
-
是否清除事件?
-
队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了 。
-
事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件。
🛴大概原理
如上图所示事件组结构体的定义,包含一个uxEventBits
成员,这是一个整数,可以根据宏设置为16位或者32位,本喵这里使用的是32位,该成员每一个比特位就代表一个事件,当该事件发生时,就将对应的比特位置1。
还有一个链表xTaskWaitingForBits
,用来管理等待事件发生而处于阻塞状态的任务,这些任务的TCB节点都在这个链表中,这些任务关心的事件没有发生(比特位为0)时,这些任务就处于阻塞状态。当某个事件发生时,就将对应的任务唤醒,并从该链表中移除,放入到就绪队列中。
如上图所示设置事件组中比特位的函数,可以看到,用户传入要设置的比特位uxBitsToSet
,在函数内部将这个值与事件组结构体中的成员uxEventBits
成员做了或运算。
如上图所示消费者任务等待事件的函数,传参时传入要等待的比特位uxBitsToWaitFor
,在函数内部会先将所有任务都挂起,然后再检查事件组中的比特位情况。
- 检查前挂起所有任务是为了保证此次检查的行为的原子性,在检查过程中不会被切换下去。
然后会获取当前事件组中标的那个整数,将这个整数和用户传入的要等待的比特位进行判断,如果事件组中要等待的比特位已经置1,立刻将该任务仍需等待的时间设置为0,也就是退出阻塞状态,放入到就绪队列中。
如果事件组中要等待的比特位仍然没有置1,那么就继续维持原本该任务的阻塞状态。
这里将事件组中比特位置一的是生产者任务,当该任务完成某个事件后,就将事先约定好的事件组的比特位置一。等待事件组中对应比特位的任务是消费者任务,它根据约定等待某个特定标志位置1,一旦置1了就说明事件发生了,就可以进行下一步动作。
🛴使用事件组的函数
创建事件组:
/* 动态创建 */
EventGroupHandle_t xEventGroupCreate( void );/* 静态创建 */
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );
- pxEventGroupBuffer:静态创建时,需要用户指定事件组结构的内存空间。
- 返回值:事件组句柄,用来控制事件组,失败返回NULL。
设置事件:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );
- xEventGroup:要设置的事件组句柄。
- uxBitsToSet:要设置的比特位,可以是一个,如bit0,也可以一次性设置多个,如(bit0 | bit 2 | bit6)。
- 返回值:返回原来事件组中的事件值(没什么意义,很有可能已经被其他任务修改了)。
设置多个比特位的时候,必须使用或运算,比如要设置bit1和bit5,在传参的时候就传(1 << 1) | (1 << 5)
。
等待事件:
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );
- xEventGroup:要等待的事件组句柄。
- uxBitsToWaitFor:要等待事件组中的比特位。
可以是一个,如bit0。
也可以是多个,如(bit0 | bit2 | bit4)。
- xClearOnExit:要等待的比特位就绪后,该函数退出前是否要清除比特位。
pdTRUE
:清除uxBitsToWaitFor指定的位。
pdFALSE
:不清除
- xWaitForAllBits:是等待uxBitsToWaitFor中的所有比特位还是某一个?
pdTRUE
:必须是uxBitsToWaitFor中所有事件都发生,该函数等待成功返回,是AND
的关系。
pdFALSE
:uxBitsToWaitFor中任意一个事件发生,该函数等待成功返回,是OR
的关系。
- xTicksToWait :等待时间,0表示立即返回,
portMAX_DELAY
表示阻塞等待。- 返回值:返回的是事件组中的整数值,如果期待的事件发生了,返回的是“事件发生后”的事件值,如果是超时退出,返回的是超时时刻的事件值。
这个函数的参数比较复杂,下面再举例说明一下:
事件组的值 | uxBitsToWaitFor | xWaitForAllBits | 说明 |
---|---|---|---|
0100 | 0101 | pdTRUE | 任务期望bit0,bit2都为1,当前值只有bit2满足,任务进入阻塞态 |
0100 | 0101 | pdFALSE | 任务期望bit0,bit2某一个为1,当前值bit2满足,任务等待成功并退出 |
我们可以使用xEventGroupWaitBits()
来等待期望的事件发生,然后再使用xEventGroupClearBits()
来清除事件的标志位,但是这两个函数之间可能被其他任务抢占,这些任务可能修改事件组的值。
所以在使用xEventGroupWaitBits()
的时候,将xClearOnExit
设置成pdTRUE
,使得对事件组的检测、清零都在xEventGroupWaitBits()
函数内部完成,这是一个原子操作。
删除事件组:
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。
同步点
有些事情是需要多任务协同的,比如吃一顿饭:
- 任务A:炒菜
- 任务B:买酒
- 任务C:摆台
A、B、C做好自己的事后,还要等别人做完,只有大家一起做完,才可开饭,在开饭的这一时刻,这三个任务的起点相同,同时开始执行,也就是位于一个同步点。
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait );
- xEventGroup:要操作的事件组句柄。
- uxBitsToSet:要设置哪些事件,调用该函数的任务完成哪些事件就设置哪几个比特位。
如任务A完成了炒菜就将bit0置一,表示炒菜事件完成。
- uxBitsToWaitFor:要等待的事件,也就是协同任务中要同步的事件。
如吃饭例子中,这里的值就是
炒作 | 买酒 | 摆台
,只有三个事件都发生时才实现同步,继续执行。
- xTicksToWait :如果期待的事件未发生,阻塞多久。可以设置为0:判断后即刻返回;可设置为
portMAX_DELAY
:一直等到成功才返回;
🛴基本使用
等待多个事件:
如上图代码所示,先创建一个事件组,用来通知事件是否发生,再创建一个队列,用来实现数据从生产者任务到消费者任务的传递。最后再创建三个任务,优先级都是1。
如上图所示,任务1进行加法运算,将运算结果放入到队列中,放入成功后再将事件组中的bit0
置一,表示加法运算事件完成。
任务2进行减法运算,将运算结果放入到队列中,放入成功后再将事件组中的bit1
置一,表示减法运算事件完成。
如上图所示,任务3负责检测加法事件和减法事件的发生,事件发生后检测函数退出前要将事件标志位清空,只有加法事件和减法事件同时发生,该检测函数才会退出,任务3才会退出阻塞状态,然后再从队列中读取加法事件和减法事件的结果,通过串口打印出来。
如上图所示,此时加法事件和减法事件的结果就打印出来了,由于三个任务在不断运行,所以不断打印结果。
如上图所示,将加法事件完成后给bit0置位的动作注释掉,此时事件组中就只有减法事件完成后置位的bit1,所以在检测事件的时候,无法检测到加法事件和减法事件同时发生,所以就会阻塞到任务3的xEventGroupWaitBits(xEventGroupCalc, (1<<0)|(1<<1), pdTRUE, pdTRUE, portMAX_DELAY);
处。
如上图所示,将任务3等待事件发生的条件改为多个事件中有一个发生即可,就是将蓝色框中的参数设为pdFALSE
,此时虽然加法事件完成以后没有将bit0
置一,但是减法任务完成后有将bit1
置一,等待函数成功返回。
加法任务虽然没有将事件标志位置位但是仍然是将结果写入到了队列中的,所以任务3也是能从队列中取出加法运算结果的。
同步点:
就拿前面吃一顿饭需要任务A,B,C来协同的例子来些代码。
如上图,首先是将三个事件要置位的比特位宏定义为标识符常量,然后创建一个事件组,再创建三个任务A,B,C,优先级分别是1,2,3,分别负责炒菜,买酒,摆台。
如上图所示三个任务的代码,每个任务都只负责自己的任务,自己的任务做完以后,调用xEventGroupSync
表示自己的任务完成,需要等待另完两人完成任务,如果人没齐则阻塞等待,如果人齐了则开饭。
如上图所示,三个任务中,炒菜任务的优先级最高,所以它最先运行,炒菜最先完成,但是此时另外两个事件还没有完成,所以炒菜任务只能阻塞等待。
买酒任务优先级高于摆台任务,所以买酒任务先运行,买酒先于摆台完成,但是此时还有一个摆台事件没有完成,所以买酒任务也只能阻塞等待。
最后摆台任务运行,摆台任务完成,另完两步也已经完成了,三步都完成就可以开饭了。
此时站在每个人的角度都知道可以开饭了,从优先级最高的炒菜任务开始挨个动筷子,这顿饭就吃起来了。
- 这个过程中,在开饭前每个任务所做的工作都不一样,但是通过事件组的同步功能,让这三个任务在开饭这一点上开始同步执行。
🚁任务通知
所谓"任务通知",你可以反过来读"通知任务",我们使用队列、信号量、事件组等等方法时,并不知道唤醒的对方是谁。
使用任务通知时,可以明确指定:通知哪个任务。
如上图,使用队列、信号量、事件组时,我们都要事先创建对应的结构体,双方通过中间的结构体通信。
如上图,但是使用任务通知时,被通知的任务结构体TCB中包含一个内部对象,可以直接接收别人发过来的"通知"。
任务通知的优势:
- 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有大的优势。
- 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的限制:
- 数据只能给某个任务独享:
使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务都可以访问这些数据。使用任务通知时,数据存放入目标任务的TCB结构体中,只有目标任务可以访问这些数据。
在日常使用中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。
- 无法缓冲数据:
使用队列时,假设队列长度为N,那么它可以保存N个数据。使用任务通知时,任务结构体中只能保存一个任务通知值,只能保持一个数据。
- 无法广播给多个任务:
使用事件组可以同时给多个任务发送事件。使用任务通知,只能发给一个任务。
- 如果发送受阻,发送方无法进入阻塞状态等待:
假设队列已经满了,使用xQueueSendToBack()
给队列发送数据时,任务可以进入阻塞状态等待发送完成。使用任务通知时,即使对方无法接收数据,发送方也无法阻塞等待,只能即刻返回错误。
🛴大概原理
如上图所示,每个任务都有一个结构体:TCB(Task Control Block),里面有2个成员:
- 一个是uint8_t类型的成员
ucNotifyState
,用来表示通知状态。 - 一个是uint32_t类型的成员
ulNotifiedValue
,用来表示通知值。
通知状态有三种取值:
取值 | 说明 |
---|---|
taskNOT_WAITING_NOTIFICATION | 任务没有在等待通知 |
taskWAITING_NOTIFICATION | 任务在等待通知 |
taskNOTIFICATION_RECEIVED | 任务接收到了通知,也被称为pending(有数据了,待处理) |
TCB中ucNotifyState
的初始默认值是taskNOT_WAITING_NOTIFICATION
,表示没有在等待任务通知。
通知值可以有多种类型:
- 计数值:就像信号量那样,表示资源数量。
- 位:就像事件组那样,每个比特位表示一个事件。
- 任意数值:就像队列,但是长度是1,只能有一个数据。
任务通知,操作的核心就是TCB中的ucNotifyState
和ulNotifiedValue
这两个成员。
🛴使用任务通知的函数
任务通知有2套函数,简化版、专业版:
- 简化版函数的使用比较简单,它实际上也是使用专业版函数实现的。
- 专业版函数支持很多参数,可以实现很多功能。
发出通知:
/* 简化版 */
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
- xTaskToNotify:要通知的任务句柄。
- 返回值:必定返回
pdPASS
。也就是通知成功。
该函数的作用:
- 使得通知值加一,就是被通知TCB中的
ulNotifiedValue++
。 - 并使得通知状态变为"pending",也就是
ucNotifyState = taskNOTIFICATION_RECEIVED
,表示有数据了、待处理。
/* 专业版 */
BaseType_t xTaskNotify(TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
- xTaskToNotify:任务句柄(创建任务时得到),给哪个任务发通知。
- ulValue:怎么使用ulValue,由eAction参数决定
- eAction:使用通知值的方式,后面列表讲解。
- 返回值:pdPASS:成功,大部分调用都会成功。
eAction
参数说明:
eAction取值 | 说明 |
---|---|
eNoAction | 仅仅是更新通知状态为"pending",未使用ulValue。 这个选项相当于轻量级的、更高效的二进制信号量。 |
eSetBits | 通知值 = 原来的通知值 (或等) ulValue,按位或。 相当于轻量级的、更高效的事件组。 |
eIncrement | 通知值 = 原来的通知值 + 1,未使用ulValue。 相当于轻量级的、更高效的二进制信号量、计数型信号量。 相当于 xTaskNotifyGive() 函数。 |
eSetValueWithoutOverwrite | 不覆盖。 如果通知状态为"pending"(表示有数据未读),则此次调用 TaskNotif 不做任何事直接返回pdFAIL |
eSetValueWithOverwrite | 覆盖。 无论如何,不管通知状态是否为"pendng",通知值 = ulValue。 |
只有eAction = eSetValueWithoutOverwrite
时,调用xTaskNotify
才有可能返回pdFAIL
,其他情况都是返回pdPASS
。
等待通知:
/* 简化版 */
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
- xClearCountOnExit:函数返回前是否清零,
pdTRUE
:把通知值清零。pdFALSE
:如果通知值大于0,则把通知值减一。- xTicksToWait:任务进入阻塞态的超时时间,0表示不等待立刻返回,
portMAX_DELAY
表示一直等待。- 返回值:返回清零或者减一前的通知值
ulNotifiedValue
。
该函数的作用:
- 如果通知值等于0,则阻塞(可以指定超时时间),说明没有通知到来。
- 当通知值大于0时,任务从阻塞态进入就绪态。
- 在
ulTaskNotifyTake
返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零。
/* 专业版 */
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
- ulBitsToClearOnEntry:在
xTaskNotifyWait
入口处,要清除通知值的哪些位?通知状态不是"pending"的情况下,才会清除。它的本意是:我想等待某些事件发生,所以先把"旧数据"的某些位清零。能清零的话:
通知值 = 通知值 & ~(ulBitsToClearOnEntry)
。比如传入0x01,表示清除通知值的bit0;传入0xffffffff即ULONG_MAX
,表示清除所有位,即把值设置为0。
- ulBitsToClearOnExit:在xTaskNotifyWait出口处,如果不是因为超时退出,而是因为得到了数据而退出时:
通知值 = 通知值 & ~(ulBitsToClearOnExit)
。在清除某些位之前,通知值先被赋给
pulNotificationValue
。比如入0x03,表示清除通知值的bit0、bit1;传入0xffffffff即ULONG_MAX
,表示清除所有位,即把值设置为0。
- pulNotificationValue:用来取出通知值。在函数退出时,使用
ulBitsToClearOnExit
清除之前,把通知值赋给pulNotificationValue
。如果不需要取出通知值,可以设为NULL。- xTicksToWait:任务进入阻塞态的超时时间。
- 返回值:
pdPASS
:成功,这表示xTaskNotifyWait
成功获得了通知;可能是调用函数之前,通知状态就是"pending";也可能是在阻塞期间,通知状态变为了"pending"。pdFAIL
:没有得到通知。
虽然函数很多,而且比较复杂,总得来说只有给出通知和等待通知两类函数,当某个或某些任务完成事件时,给指定任务发出通知,这是多对一的通知形式。等待通知时,只能等待自己TCB中的通知值。
发出通知的任务不会阻塞,都是立刻返回。如果是不覆盖的通知,通知失败会返回pdFAIL
,其他情况下返回的都是pdPASS
。
等待通知时可以设置等待方式,可以立刻返回,也可以设置超时时间,还可以设置成一直阻塞,直到通知到来的方式。
🛴基本使用
如上图,使用任务通知之前,需要先定义宏开关configUSE_TASK_NOTIFICATIONS
。
轻量级信号量:
任务通知值ulNotifiedValue
作为信号量,申请信号量时减减通知值,归还信号量时加加通知值。
如上图,创建两个任务,优先级都是1,一个任务用来增加通知值,另一个任务用来减小通知值。
如上图所示,任务1进行计算,计算完成后通知任务2,使用的是简化版的通知函数,仅给任务2的通知值加加,连续加十次。
任务2等待到通知以后,读取通知值,然后将通知值减一,并且打印出计算结果,以及减一之前的通知值读取次数。
如上图,可以看到,任务2每读取一次,通知值减一,直到减为0后,阻塞不动。
如上图,将ulTaskNotifyTake
中的第一个参数由pdFALSE
改为pdTRUE
,让任务2读取完一次通知值以后将通知值清零。
如上图所示,虽然任务2将通知值增加了十次,但是任务1取了一次以后就将通知值清零了,此时任务2就不会再次读取了,阻塞不动。
与信号量进行对比:
操作 | 信号量 | 轻量级信号量 |
---|---|---|
创建 | SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount ); | 无 |
Give | xSemaphoreGive( SemaphoreHandle_t xSemaphor ); | BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify); |
Take | xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xBlockTime); | uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait); |
使用任务通知实现的轻量级信号量,使用起来更加简便,都不用创建信号量,直接操作即可。
轻量级队列:
使用通知值ulNotifiedValue
来传递数据,只能传送一个值,所以相当于长度为1的队列。
如上图,任务1计算完成后,将结果覆盖式的写入任务2TCB中的通知值中,xTaskNotify
的最后一个参数是eSetValueWithOverwrite
,表示覆盖式写入。每写入一次后将计算结果sum
再加加,一共写入十次。
任务2等待到任务1的通知后,读取自己CTB中的通知值,并且打印出来。
如上图所示,此时任务2只读取一次通知值,而且读取的是任务最后一次覆盖写入的值,所以说用任务通知来实现队列只能传递一个数据。
与队列的对比:
操作 | 队列 | 使用任务通知实现队列 |
---|---|---|
创建 | QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize); | 无 |
发送 | BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait ); | BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction ); |
接收 | BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait); | BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait); |
使用任务通知实现的轻量级队列,虽然只能传递一个数据,但是在使用上也更加简洁,不用创建队列,操作函数也比较简便。
轻量级事件组:
操作的也是通知值ulNotifiedValue
,每个比特位都代表一个事件。
如上图所示,创建一个队列,用来存放事件值,创建互斥锁,让三个任务独立使用串口,再创建三个任务,优先级都是1,任务1进行加法运行,任务2进行减法运行,任务3检测这两个事件的完成情况。
如上图所示,任务1进行加法运算,将结果写入到队列中,然后将任务3中任务值的bit0置一,表示加法事件完成。任务2继续减法运算,将结果写入到队列中,然后将任务3中任务值的bit1置一,表示减法事件完成。
如上图所示,任务3等待通知值,等待成功后,判断bit0和bit1的情况,如果都为1则表示加法事件和减法事件都完成,否则就只完成一件。
如上图所示,可以看到,此时任务3的通知值中,bit0和bit1都是1,表示两个事件都完成,且打印出了它们的运算结果。
如上图,稍作修改,当任务2完成减法计算时,不会将任务3通知值的bit1置一,只是将计算结果写入到队列。
如上图所示,此时任务3仍然会等待成功,只是因为加法事件完成而等待成功。
- 轻量级事件组和事件组的区别就在于,它不能同时等待多个事件。只要有事件发生它就会等待成功。
与事件组的对比:
操作 | 事件组 | 使用任务通知实现事件组 |
---|---|---|
创建 | EventGroupHandle_t xEventGroupCreate( void ) | 无 |
设置事件 | EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet); | BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction); |
等待事件 | EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait ); | BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait); |
使用任务通知实现的轻量级事件组,虽然无法实现同时等待多个事件,但是使用起来同样更简洁。
🚁总结
事件组虽然不能直接存放数据本身,但是它可以同时检测多个事件,同时等待多个事件的发生,而且可以做到让不同事件处于一个同步点。
任务通知是非常方便的一种任务间通信的方式,一般轻量级的通信都可以使用任务通知的方式,操作简单,函数简洁。
相关文章:

【RTOS学习】事件组 | 任务通知
🐱作者:一只大喵咪1201 🐱专栏:《RTOS学习》 🔥格言:你只管努力,剩下的交给时间! 事件组 | 任务通知 🚁事件组🛴大概原理🛴使用事件组的函数同步…...

【TES605】基于Virtex-7 FPGA的高性能实时信号处理平台
板卡概述 TES605是一款基于Virtex-7 FPGA的高性能实时信号处理平台,该平台采用1片TI的KeyStone系列多核DSP TMS320C6678作为主处理单元,采用1片Xilinx的Virtex-7系列FPGA XC7VX690T作为协处理单元,具有2个FMC子卡接口,各个处理节…...
Java Azure开发 使用已有token字符串创建GraphServiceClient
一、背景说明 在已有的项目中,已经获取到了Graph的AccessToken并保存在内存里面。所以不希望再通过client secret或者certificate去创建GraphServiceClient对象。希望使用现有的token字符串来创建初始化创建GraphServiceClient从而来实现Graph其他API功能。 二、具体…...

【Qt】消息机制和事件
文章目录 事件event()事件过滤器案例:检测鼠标事件案例:定时器 事件 事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事…...
爬虫模拟用户登录
使用爬虫模拟用户登录过程一般包括以下几个步骤: 导入所需的库:一般需要导入requests和BeautifulSoup库来发送HTTP请求和解析HTML。 import requestsfrom bs4 import BeautifulSoup 发送GET请求获取登录页面:使用requests库发送GET请求&#…...

asp.net企业招聘管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio计算机毕业设计
一、源码特点 asp.net 企业招聘管理系统 是一套完善的web设计管理系统,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为vs2010,数据库为sqlserver2008,使用c#语 言开发 asp.net企业招聘管理系统 二、功…...
艾泊宇产品战略:假如中国禁止直播带货,网红和店家该如何应对?
近日,印尼宣布将禁止直播带货,这一消息引起了广泛关注。有人欢喜有人忧,很多人为此拍手叫好,但也有很多人为此感到担忧。 今天,我们就来做一个战略推演,假设中国也禁止直播带货,入局直播带货者…...

C#调用C/C++从零深入讲解
C#调用非托管DLL从零深入讲解 一、结构对齐 结构对齐是C#调用非托管DLL的必备知识。 在没有#pragma pack声明下结构体内存对齐的规则为: 第一个成员的偏移量为0,每个成员的首地址为自身大小的整数倍子结构体的第一个成员偏移量应当是子结构体最大成员的整数倍结构体总大小…...

MyBatis篇---第五篇
系列文章目录 文章目录 系列文章目录一、MyBatis 中见过什么设计模式?二、MyBatis 中比如 UserMapper.java 是接口,为什么没有实现类还能调用? 一、MyBatis 中见过什么设计模式? 二、MyBatis 中比如 UserMapper.java 是接口&#…...

十三水中各种牌型判断LUA版
近期回归程序行业,由于业务需求需要做十三水游戏,什么是十三水就不在多讲,下面是判断十三水牌型的方法(带大小王) GetSSSPaiType {}; local this GetSSSPaiType; local huaseTable {}; local numTable {}; functi…...

2023.10.19 关于设计模式 —— 单例模式
目录 引言 单例模式 饿汉模式 懒汉模式 懒汉模式线程安全问题 分析原因 引言 设计模式为编写代码的 约定 和 规范 阅读下面文章前建议点击下方链接明白 对象 和 类对象 对象和类对象 单例模式 单个实例(对象)在某些场景中有特定的类,…...
4个鲜为人知的Python迭代过滤函数
在Python中,迭代器可以帮助你编写更多Pythonic的代码,并在处理长序列时提高效率,内置的itertools模块提供了几个有用的函数来创建迭代器。 当你只需要遍历迭代器、检索序列中的元素并对其进行处理,而无需将它们存储在内存中时&am…...

使用logger.error(“自定义错误信息描述“,e)将错误信息输出到日志文件上
之前一直用e.getMessage()来获取错误信息 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;RestController public class ClassF…...

音乐的数字未来:虚拟演唱会与TikTok的巅峰融合
在数字时代,音乐产业正在经历着革命性的变革。虚拟演唱会与TikTok的融合正引领着音乐的数字未来,为艺术家、粉丝和创作者带来了前所未有的互动性和娱乐体验。本文将深入探讨这一巅峰融合,以揭示音乐产业的新前景。 虚拟演唱会的崛起 虚拟演唱…...

基于图像识别的跌倒检测算法 计算机竞赛
前言 🔥 优质竞赛项目系列,今天要分享的是 基于图像识别的跌倒检测算法 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🧿 更多资料, 项目分享: https://gitee.com/dancheng-senior/…...

NSS [SWPUCTF 2021 新生赛]PseudoProtocols
NSS [SWPUCTF 2021 新生赛]PseudoProtocols 先看题目,题目要求我们先找到hint.php。 看这个get请求头,我们先用php://filter协议读一波 得到提示,让我们前往/test2222222222222.php 源码如下 <?php ini_set("max_execution_time&qu…...

字节码进阶之JVM Attach API详解
字节码进阶之JVM Attach API详解 文章目录 字节码进阶之JVM Attach API详解附加到虚拟机加载代理和获取信息分离虚拟机 使用Attach API的基本步骤1. **获取虚拟机实例**:2. **附加到虚拟机**:3. **加载代理或获取信息**4. **从虚拟机分离**:…...

Kubernetes 部署 kubeflow1.6.1
前言 安装前请注意捋清楚版本关系,如kubeflow版本对应的K8S版本及其相关工具版本等等 我们此处使用的是是kubeflow-1.6.1和K8s-v1.22.8 单机部署 部署K8S 初始化Linux 1.关闭selinux setenforce 0 && sed -i "s/SELINUXenforcing/SELINUXdisable…...

设计模式:建造者模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)
上一篇《策略模式》 下一篇《适配器模式》 简介: 建造者模式,它是一种对象构建模式,它提供了一种构建对象的最佳方式。这种模式适用于当对象的构建过程需要涉及到多个部分ÿ…...
Maxon Cinema 4D 2024:打造独一无二的视觉效果 模拟模块大更新
在视觉效果和3D建模领域,Maxon的Cinema 4D一直以其卓越的性能和创新的功能引领着时代潮流。今天,我们很高兴地宣布推出最新版本——Maxon Cinema 4D 2024(C4D 2024),它将再次提升行业标准,为设计师提供更强…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...

Tauri2学习笔记
教程地址:https://www.bilibili.com/video/BV1Ca411N7mF?spm_id_from333.788.player.switch&vd_source707ec8983cc32e6e065d5496a7f79ee6 官方指引:https://tauri.app/zh-cn/start/ 目前Tauri2的教程视频不多,我按照Tauri1的教程来学习&…...
raid存储技术
1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划,涵盖存储系统的布局、数据存储策略等,它明确数据如何存储、管理与访问,为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...
Qt Quick Controls模块功能及架构
Qt Quick Controls是Qt Quick的一个附加模块,提供了一套用于构建完整用户界面的UI控件。在Qt 6.0中,这个模块经历了重大重构和改进。 一、主要功能和特点 1. 架构重构 完全重写了底层架构,与Qt Quick更紧密集成 移除了对Qt Widgets的依赖&…...

npm安装electron下载太慢,导致报错
npm安装electron下载太慢,导致报错 背景 想学习electron框架做个桌面应用,卡在了安装依赖(无语了)。。。一开始以为node版本或者npm版本太低问题,调整版本后还是报错。偶尔执行install命令后,可以开始下载…...
Cursor AI 账号纯净度维护与高效注册指南
Cursor AI 账号纯净度维护与高效注册指南:解决限制问题的实战方案 风车无限免费邮箱系统网页端使用说明|快速获取邮箱|cursor|windsurf|augment 问题背景 在成功解决 Cursor 环境配置问题后,许多开发者仍面临账号纯净度不足导致的限制问题。无论使用 16…...