RT_thread(六)线程间同步之事件集


事件集

一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。

例子:出游这一事件,组织者在等人(事件)
张三:到了
李四:到了
王五:到了
组织者表示都到了,出发

事件集工作机制

事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。
一个线程对多个事件:任意一个事件唤醒线程,或多个事件唤醒线程
多个线程对多个事件:多个事件可以用32位无符号整型变量表示(和优先级一样),一位数表示一个事件(一位数表示一个优先级),线程需要的位数(事件)通过“逻辑与” 或“逻辑或”计算出0或1(优先级取最低位),事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。

RT-Thread 定义的事件集有以下特点:
1)事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;
2)事件仅用于同步,不提供数据传输功能;
3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

在 RT-Thread 中,每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。

线程中事件设置

在这里插入图片描述

下图中事件集为上图event_set成员

在这里插入图片描述

如图所示,一个事件集有32位,其中标志位第一位和第三十位被置1,而线程1只要关注第一位和第三十位,

event_info事件信息标志位

RT_EVENT_FLAG_AND(逻辑与):线程 1 只有在事件 1 和事件 30 都发生以后才会被触发唤醒
RT_EVENT_FLAG_OR(逻辑或):事件 1 或事件 30 中的任意一个发生都会触发唤醒线程 1
RT_EVENT_FLAG_CLEAR(清除标记):线程 1 唤醒后将主动把事件 1 和事件 30 清为零,否则事件标志将依然存在(即置 1)

事件集控制块

struct rt_event
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint32_t          set;                           /**< 事件集设置位 */
};
typedef struct rt_event *rt_event_t;
#endif

ipc对象继承

struct rt_ipc_object
{
    struct rt_object parent;                            //name、type、flag

    rt_list_t        suspend_thread;                    //等待事件线程,看出那些线程和事件集相关
};

事件操作

操作和信号量一样
对一个事件集的操作包含:初始化 / 脱离事件集、创建 / 删除事件集、发送事件、接收事件。
在这里插入图片描述
代码顺序为内核中ipc.c顺序,即编写顺序

初始化 / 脱离事件集

初始化
静态事件集对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中。在使用静态事件集对象前,需要先行对它进行初始化操作。

rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)
{
    /* parameter check */
    RT_ASSERT(event != RT_NULL);//核查是否为事件集

    /* init object */
    rt_object_init(&(event->parent.parent), RT_Object_Class_Event, name);//对象设置为事件集对象,并设置名字

    /* set parent flag */
    event->parent.parent.flag = flag;//设置对象标志位

    /* init ipc object */
    rt_ipc_object_init(&(event->parent));//ipc对象初始化,对象中有个链表进行初始化

    /* init event */
    event->set = 0;//无事件发生

    return RT_EOK;
}
RTM_EXPORT(rt_event_init);

脱离
系统不再使用 rt_event_init() 初始化的事件集对象时,通过脱离事件集对象控制块来释放系统资源。脱离事件集是将事件集对象从内核对象管理器中脱离。

rt_err_t rt_event_detach(rt_event_t event)
{
    /* parameter check */
    RT_ASSERT(event != RT_NULL);//是否为事件
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event);//对象是否为事件
    RT_ASSERT(rt_object_is_systemobject(&event->parent.parent));//是否为静态对象

    /* resume all suspended thread */
    rt_ipc_list_resume_all(&(event->parent.suspend_thread));//唤醒等待线程,原来挂起在信号量上的等待线程将获得 - RT_ERROR 的返回值

    /* detach event object */
    rt_object_detach(&(event->parent.parent));//从对象链表中移除

    return RT_EOK;
}
RTM_EXPORT(rt_event_detach);

创建 / 删除事件集

创建
当创建一个事件集时,内核首先创建一个事件集控制块,然后对该事件集控制块进行基本的初始化

rt_event_t rt_event_create(const char *name, rt_uint8_t flag)
{
    rt_event_t event;//创建事件

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* allocate object */
    event = (rt_event_t)rt_object_allocate(RT_Object_Class_Event, name);//分配内存
    if (event == RT_NULL)
        return event;

    /* set parent */
    event->parent.parent.flag = flag;//设置标志位

    /* init ipc object */
    rt_ipc_object_init(&(event->parent));//对象初始化,里面有等待事件的挂起线程

    /* init event */
    event->set = 0;//没有事件发生

    return event;
}
RTM_EXPORT(rt_event_create);

删除
在调用 rt_event_delete 函数删除一个事件集对象时,应该确保该事件集不再被使用。在删除前会唤醒所有挂起在该事件集上的线程(线程的返回值是 - RT_ERROR),然后释放事件集对象占用的内存块。

rt_err_t rt_event_delete(rt_event_t event)
{
    /* parameter check */
    RT_ASSERT(event != RT_NULL);//事件是否为空
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event);//对象类型是否事件
    RT_ASSERT(rt_object_is_systemobject(&event->parent.parent) == RT_FALSE);//是否为静态对象,静态对象不能删

    RT_DEBUG_NOT_IN_INTERRUPT;//确定不能在中断中

    /* resume all suspended thread */
    rt_ipc_list_resume_all(&(event->parent.suspend_thread));//唤醒线程,返回失败

    /* delete event object */
    rt_object_delete(&(event->parent.parent));//移除对象,并删除内存(此操作不能再中断中运行)

    return RT_EOK;
}
RTM_EXPORT(rt_event_delete);

发送事件

使用该函数接口时,通过参数 set 指定的事件标志来设定 event 事件集对象的事件标志值,然后遍历等待在 event 事件集对象上的等待线程链表,判断是否有线程的事件激活要求与当前 event 对象事件标志值匹配,如果有,则唤醒该线程。

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)
{
    struct rt_list_node *n;//
    struct rt_thread *thread;//线程指针
    register rt_ubase_t level;//中断标志位
    register rt_base_t status;
    rt_bool_t need_schedule;//调度锁

    /* parameter check */
    RT_ASSERT(event != RT_NULL);//事件不为空
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event);//对象类型为事件

    if (set == 0)//没发事件,返回失败
        return -RT_ERROR;

    need_schedule = RT_FALSE;//调度锁关

    /* disable interrupt */
    level = rt_hw_interrupt_disable();//关中断

    /* set event */
    event->set |= set;//发送的事件添加到事件集中

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(event->parent.parent)));
    
    if (!rt_list_isempty(&event->parent.suspend_thread))//有等待事件
    {
        /* search thread list to resume thread */
        n = event->parent.suspend_thread.next;//取第一个等待事件
        while (n != &(event->parent.suspend_thread))//遍历,后面会n=n->next
        {
            /* get thread */
            thread = rt_list_entry(n, struct rt_thread, tlist);//获取线程控制块

            status = -RT_ERROR;
            if (thread->event_info & RT_EVENT_FLAG_AND)//线程等待事件集命令为与
            {
                if ((thread->event_set & event->set) == thread->event_set)//触发,状态为成功
                {
                    /* received an AND event */
                    status = RT_EOK;
                }
            }
            else if (thread->event_info & RT_EVENT_FLAG_OR)//线程等待事件集命令为或
            {
                if (thread->event_set & event->set)//是否触发
                {
                    /* save recieved event set */
                    thread->event_set = thread->event_set & event->set;//保存收到的事件
                    /* received an OR event */
                    status = RT_EOK;
                }
            }

            /* move node to the next */	
          n = n->next;//下一个等待线程

            /* condition is satisfied, resume thread */
            if (status == RT_EOK)//条件满足,恢复线程
            {
                /* clear event */
                if (thread->event_info & RT_EVENT_FLAG_CLEAR)//线程等待事件命令为清除
                   event->set &= ~thread->event_set;//在发送中清除,再次触发线程

                /* resume thread, and thread list breaks out */
                rt_thread_resume(thread);//唤醒线程

                /* need do a scheduling */
                need_schedule = RT_TRUE;//调度锁打开
            }
        }
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);//开中断

    /* do a schedule */
    if (need_schedule == RT_TRUE)
        rt_schedule();//调度

    return RT_EOK;
}
RTM_EXPORT(rt_event_send);

接收

内核使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或“逻辑或”来选择如何激活线程,使用 “逻辑与” 参数表示只有当所有等待的事件都发生时才激活线程,而使用 “逻辑或” 参数则表示只要有一个等待的事件发生就激活线程
当用户调用这个接口时,系统首先根据 set 参数和接收选项 option 来判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位,然后返回(其中 recved 参数返回接收到的事件);如果没有发生,则把等待的 set 和 option 参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回 - RT_ETIMEOUT。

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)//用于保存接收到的事件标志结果,用户通过它的值判断是否成功接收到事件
{
    struct rt_thread *thread;
    register rt_ubase_t level;
    register rt_base_t status;

    RT_DEBUG_IN_THREAD_CONTEXT;

    /* parameter check */
    RT_ASSERT(event != RT_NULL);//检查事件句柄 event 是否有效,如果它是未定义或者未创建的事件句柄,那么是无法接收事件的
    RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event);//对象类型是否为事件

    if (set == 0)
        return -RT_ERROR;

    /* init status */
    status = -RT_ERROR;//初始化状态
    /* get current thread */
    thread = rt_thread_self();//获取当前线程
    /* reset thread error */
    thread->error = RT_EOK;//重置错误码

    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(event->parent.parent)));

    /* disable interrupt */
    level = rt_hw_interrupt_disable();//关中断

    /* check event set */
    if (option & RT_EVENT_FLAG_AND)//接收命令为或,全都有才运行
    {
        if ((event->set & set) == set)
            status = RT_EOK;
    }
    else if (option & RT_EVENT_FLAG_OR)//接收命令为与,至少有一个就可以运行
    {
        if (event->set & set)
            status = RT_EOK;
    }
    else
    {
        /* either RT_EVENT_FLAG_AND or RT_EVENT_FLAG_OR should be set */
        RT_ASSERT(0);//应设置 RT_EVENT_FLAG_AND 或 RT_EVENT_FLAG_OR
    }

    if (status == RT_EOK)
    {
        /* set received event */
        if (recved)
            *recved = (event->set & set);//满足接收事件的条件,则返回接收的事件,读取 recved 即可知道接收到了哪个事件

        /* received event */
        if (option & RT_EVENT_FLAG_CLEAR)//如果指定的 option 接收选项选择了 RT_EVENT_FLAG_CLEAR,
                                       //在接收完成的时候会清除对应的事件集合的标志位
            event->set &= ~set;
    }
    else if (timeout == 0)
    {
        /* no waiting */
        thread->error = -RT_ETIMEOUT;//那么接收不到事件就不等待,直接返回-RT_ETIMEOUT 错误码
    }
    else
    {
        /* fill thread event info */
        thread->event_set  = set;//设置事件信息
        thread->event_info = option;

        /* put thread to suspended thread list */
        rt_ipc_list_suspend(&(event->parent.suspend_thread),//线程加入此事件的等待链表
                            thread,
                            event->parent.parent.flag);

        /* if there is a waiting timeout, active thread timer */
        if (timeout > 0)//可以等,设置线程定时器
        {
            /* reset the timeout of thread timer and start it */
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout);
            rt_timer_start(&(thread->thread_timer));//启动定时器
        }

        /* enable interrupt */
        rt_hw_interrupt_enable(level);//开中断

        /* do a schedule */
        rt_schedule();//启动调度器

        if (thread->error != RT_EOK)//线程错误码不是RT_EOK,说明设置失败了
        {
            /* return error */
            return thread->error;
        }

        /* received an event, disable interrupt to protect */
        level = rt_hw_interrupt_disable();//开中断

        /* set received event */
        if (recved)
            *recved = thread->event_set;//返回接收到的事件
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);//开中断

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(event->parent.parent)));

    return thread->error;
}
RTM_EXPORT(rt_event_recv);
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RT_thread 是一款开源的实时操作系统,支持多线程的调度和管理。在多线程应用程序中,线程的同步是非常重要的一环,可以保证线程之间的协作和数据的一致性。本实验介绍在 RT_thread 中如何进行线同步。 实验目的: 1. 熟悉 RT_thread 线同步方法; 2. 掌握 RT_thread 常用的同步方式; 实验器材: 1. STMicroelectronics Nucleo-F429ZI 板卡; 2. Mini USB 线。 实验环境: 1. Ubuntu 18.04 LTS 系统; 2. MDK-ARM V5.29 编译器; 3. RT_thread 3.0.2 实时操作系统; 实验步骤: 1. 创建两个线程 thread1 和 thread2; 2. 在 thread1 中使用互斥锁 MTX1,并打印“thread1 get MTX1”; 3. 在 thread2 中使用互斥锁 MTX1,并打印“thread2 get MTX1”; 4. 分别让 thread1 和 thread2 休眠一段时间后,释放互斥锁 MTX1; 5. 使用 semaphore1 信号量来控制 thread1 和 thread2 的执行次序; 6. 在 main 函数中调用 rt_thread_startup(thread1) 和 rt_thread_startup(thread2); 7. 编译、烧录程序,观察串口输出结果。 代码实现: #include <rtthread.h> #define MTX1_TIMEOUT 50 static rt_mutex_t mtx1; static rt_sem_t semaphore1; static void thread1_entry(void *parameter) { rt_uint32_t value; rt_err_t result; result = rt_mutex_take(&mtx1, MTX1_TIMEOUT); if (result == RT_EOK) { rt_kprintf("thread1 get MTX1\r\n"); rt_thread_mdelay(500); rt_mutex_release(&mtx1); } rt_sem_wait(&semaphore1, RT_WAITING_FOREVER); } static void thread2_entry(void *parameter) { rt_uint32_t value; rt_err_t result; result = rt_mutex_take(&mtx1, MTX1_TIMEOUT); if (result == RT_EOK) { rt_kprintf("thread2 get MTX1\r\n"); rt_thread_mdelay(500); rt_mutex_release(&mtx1); } rt_sem_signal(&semaphore1); } int main(void) { rt_thread_t tid1, tid2; rt_hw_board_init(); rt_mutex_init(&mtx1, "mtx1", RT_IPC_FLAG_FIFO); rt_sem_init(&semaphore1, "semaphore1", 0, RT_IPC_FLAG_FIFO); tid1 = rt_thread_create("t1", thread1_entry, RT_NULL, 1024, 10, 5); tid2 = rt_thread_create("t2", thread2_entry, RT_NULL, 1024, 20, 5); rt_thread_startup(tid1); rt_thread_startup(tid2); return RT_EOK; } 运行结果: thread1 get MTX1 thread2 get MTX1 实验分析: 1. 程序创建了两个线程 thread1 和 thread2,使用互斥锁 MTX1 来同步线程的访问; 2. thread1 先获取互斥锁 MTX1,并打印“thread1 get MTX1”,然后延时 500ms 之后释放互斥锁 MTX1; 3. thread2 延时一段时间后获取互斥锁 MTX1,并打印“thread2 get MTX1”,然后释放互斥锁 MTX1; 4. 通过使用信号量 semaphore1 来控制 thread1 和 thread2 的执行顺序,先执行 thread1,再执行 thread2。 结论: 1. RT_thread 支持多种同步方式,如互斥锁、信号量、事件等; 2. 通过使用同步方法,可以保证多线程应用程序的正确性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值