事件集
事件集是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。
下面以坐公交为例说明事件,在公交站等公交时可能有以下几种情况:
①P1 坐公交去某地,只有一种公交可以到达目的地,等到此公交即可出发。
②P1 坐公交去某地,有 3 种公交都可以到达目的地,等到其中任意一辆即可出发。
③P1 约另一人 P2 一起去某地,则 P1 必须要等到 “同伴 P2 到达公交站” 与“公交到达公交站”两个条件都满足后,才能出发。
可以将P1去某地视为线程,将公交到达公交站,同伴P2到达公交站视为事件的发送。
①是特定事件唤醒线程;②任意单个事件唤醒线程;③是多个事件同时发生才唤醒线程。
事件集工作机制
事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。
即一个线程与多个事件的关系可设置为:任意一个事件唤醒线程,或几个事件都到达后次啊唤醒线程进行后续的处理;同样,事件可以是多个线程同步多个事件。
多事件的集合可以用一个32位无符号整型变量来表示,变量的每一位代表一个事件,线程通过“逻辑与”或“逻辑或”将一个或多个事件关联起来,形成事件组合。
RTT定义的事件集有以下特点:
- 事件只与线程相关,事件间相互独立:每个线程可拥有32个事件标志,采用一个32bit无符号整型数进行记录,每一个bit代表一个事件;
- 事件仅用于同步,不提供数据传输功能;
- 事件无排队性,即多次向线程发送同一事件(如果线程还未来得及读走),其效果等同于只发送一次。
在 RT-Thread 中,每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。
当线程等待事件同步时,可以通过32个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。
struct rt_ipc_object
{struct rt_object parent;rt_list_t suspend_thread;
};
struct rt_event
{struct rt_ipc_object parent;rt_uint32_t set;struct rt_spinlock spinlock;
};
创建事件集
当创建一个事件集时,内核首先创建一个事件集控制块,然后对该事件集控制块进行基本的初始化,创建事件集使用下面的函数接口:
rt_event rt_event_create(const char* name, rt_uint8_t flag);
调用该函数接口时,系统会从对象管理器中分配事件集对象,并初始化这个对象,然后初始化父类IPC对象。
发送事件
发送事件函数可以发送事件集中的一个或多个事件,如下:
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
使用该函数接口时,通过参数set指定的事件标志来设定event事件集对象的事件标志值,然后遍历等待在event事件集对象上的等待线程链表,判断是否有线程的事件激活要求与当前event对象时间标志值匹配,如果有则唤醒该线程。
接收事件
内核使用32位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收32个事件,内核可以通过指定选择参数 “逻辑与” 或“逻辑或”来选择如何激活线程,使用 “逻辑与” 参数表示只有当所有等待的事件都发生时才激活线程,而使用 “逻辑或” 参数则表示只要有一个等待的事件发生就激活线程。接收事件使用下面的函数接口:
rt_err_t rt_event_recv(rt_event_t event,rt_uint32_t set,rt_uint8_t option,rt_int32_t timeout,rt_uint32_t* recved);
当用户调用这个接口时,系统首先根据set参数和接收选项option判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位,然后返回**(其中recved参数返回接收到的事件)**
如果没有发生,则把等待的set和option参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回 - RT_ETIMEOUT。
事件集应用示例
这是事件集的应用例程,例子中初始化了一个事件集,两个线程。
一个线程等待自己关心的事件发生,另外一个线程发送事件。
#include <rtthread.h>#define THREAD_PRIORITY 9
#define THREAD_TIMESLICE 5#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)/* 事件控制块 */
static struct rt_event event;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;static void thread1_recv_event(void *param)
{rt_uint32_t e;if(rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITTING_FOREVER, &e) == RT_EOK){rt_kprintf("0x%x\n",e);}t_kprintf("thread1: delay 1s to prepare the second event\n");rt_thread_mdelay(1000);/* 第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志 */if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &e) == RT_EOK){rt_kprintf("thread1: AND recv event 0x%x\n", e);}rt_kprintf("thread1 leave.\n");
}