用事件组实现多事件的单向同步
概述
相比二值信号量用于处理一个事情的发生,事件组是处理一组事件发生的同步组件。
事件组可以看作若干个二值信号量的组合,事件组中的每一个 bit 都代表某事件是否发生。此外,事件组还提供了如下的功能:
1)处理不同事件的逻辑关系的功能。可以是逻辑与,即所有事件都发生(对应所有的bit位都置位)才触发某种行为。也可以是逻辑或,即关注的事件中,只要发生一个(对应关注的bit位中有至少一个置位)就触发某种行为。
2)可选择的清除功能,即对应的事件触发后,是否将该事件的 bit 位复位。在不清除对应 bit 位的情况下,可以实现唤醒多个等待该 bit 的 任务(仅最后一个接收事件的任务负责清除该事件),实现“广播”功能。
虽然上节讲述的计数信号量可以用于多任务的同步(通过循环获取或者释放信号量实现),但当信号有多个发送方发送到接收方时,接收方并不知道当前正在处理的信号是哪个发送方发送的。使用事件组可以让不同的发送方使用不同的 bit位,如此接收方可以知道是哪个发送方正在发送事件。
事件组的优势:
1)便于实现多个事件的同时同步,比如在一个任务里监听多个事件(每创建一个任务都要消耗较多资源,因此仅创建一个任务的情况下能节省很多资源)。这些事件可以是多个中断、多个任务等。
2)接收方可以选择是否消耗掉该事件(即使用后是否对该 bit 进行清零)。
3)接收方可以通过 bit 位知道此时发送事件的发送方,从而对不同的发送方可以执行差别化的处理。
不足:
1)虽然可以解决识别发送方的需求,但无法进一步地说明发送信号的目的(只能说有事件发生了,但具体是什么不知道)。
2)每个标志位只能取 0 或 1,无法对事件进行计数。因此,当多次向任务设置同一个标志位,但任务没有及时对该标志位进行清零的话,则等效于仅设置了一次该标志。
需求及功能解析
示例演示事件组的基本使用方法,即任务1、任务2向任务3通过事件组发送事件,任务3接收事件,并打印是哪个任务在发送事件。
事件组的操作主要是创建、设置bits、等待bits:
/* 创建一个事件组,返回它的句柄。
* 此函数内部会分配事件组结构体
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreate( void );
/* 设置事件组中的位
* 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)
*/
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, // 事件组
const EventBits_t uxBitsToSet // 设置哪些位?可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
);
/* 等待事件组中的位
* 返回值: 如果期待的事件发生了,返回的是解除阻塞时的事件值;
如果是超时退出,返回的是超时时刻的事件值。
*/
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, // 事件组
const EventBits_t uxBitsToWaitFor, // 等待哪些位
const BaseType_t xClearOnExit, // 函数提出前是否要清除事件?pdTRUE: 清除uxBitsToWaitFor指定的位,pdFALSE: 不清除
const BaseType_t xWaitForAllBits, // 是"AND"还是"OR"?pdTRUE: 等待的位,全部为1;pdFALSE: 等待的位,某一个为1即可
TickType_t xTicksToWait ); // 阻塞时间
/* 设置并等待事件组中的位
* 返回值: 如果期待的事件发生了,返回的是解除阻塞时的事件值;
如果是超时退出,返回的是超时时刻的事件值。
*/
EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup, // 事件组
const EventBits_t uxBitsToSet, // 设置哪些位?可以用来设置多个位
const EventBits_t uxBitsToWaitFor, // 等待哪些位
TickType_t xTicksToWait)// 阻塞时间
示例解析
示例输出:
This is esp32 chip with 2 CPU core(s), WiFi/BT/BLE, Minimum free heap size: 295340 bytes
TASK3: wait events ok, now bits=0x0003
TASK3: wait events ok, now bits=0x0001
TASK3: wait events ok, now bits=0x0002
TASK3: wait events ok, now bits=0x0001
TASK3: wait events ok, now bits=0x0002
TASK3: wait events ok, now bits=0x0001
TASK3: wait events ok, now bits=0x0002
TASK3: wait events ok, now bits=0x0001
TASK3: wait events ok, now bits=0x0002
TASK3: wait events ok, now bits=0x0001
TASK3: wait events ok, now bits=0x0002
示例中任务1、任务2分别通过 bit0、bit1对任务 3发送事件,任务3可以通过当前事件组的 bits 的值,判断当前哪个任务中有事件发生,如上述 bits=0x0001 的情况,代表任务 1发送了事件;上述 bits=0x0002 的情况,代表任务 2发送了事件;上述 bits=0x0003 的情况,代表任务 1、任务2都发送了事件;
讨论
在进入任务通信的章节后,一定要注意 RTOS 中存在的一个无形的导演,即任务调度器,任务之间的优先级将是通信后系统整体运行实时性、结果正确性的关键因素之一。感兴趣的小伙伴可以回顾RTOS 中的任务调度与三种任务模型。
函数 xEventGroupSync()
实现了即发送事件,也等待事件的机制,可以用于更复杂的同步场景,如 A 发送事件“酒买好了”,然后进入等待“菜上桌了”事件,然后 B 发送事件“菜上桌了”,然后进入等待“酒买好了”事件,两者都成功等到事件时,则继续向下执行“开干”事件。
总结
1)事件组(EventGroup)提供了管理一组事件的能力,并且可以处理这些事件的逻辑关系。
2)事件组可以用于多任务的同步,但没有提供对事件的计数和更多数据信息的能力。
3)件组的操作主要是创建、设置bits、等待bits。
资源链接
1)Learning-FreeRTOS-with-esp32 系列博客介绍
2)对应示例的 code 链接 (点击直达代码仓库)
3)下一篇:用邮箱实现多事件的单向同步