RT-THREAD 内核快速入门(三) 信号量,互斥量,事件

系列文章目录

RT-THREAD 内核快速入门(一)线程

RT-THREAD 内核快速入门(二)定时器

RT-THREAD 内核快速入门(四)邮箱,消息队列,信号

RT-THREAD 内核快速入门(五)内存管理与中断管理

基于STM32Cubemx移植Rt-thread-nano
这是这个系列的第三篇,内核快速入门之线程同步,将学习信号量,互斥量,事件对线程进行同步,核心都是围绕这线程的就绪与挂起这两个关键词进行学习,对线程进行管理。

文章目录

  • 系列文章目录
  • 前言
  • 一、信号量
    • 简单的例子
    • 同步信号运行规律
    • 信号量常用的运用场景
  • 二、互斥量
    • 联系
    • 使用场景:
  • 三、事件
    • 简单例子
  • 四、线程同步的关系
    • 联系
  • 五、总结


前言

在裸机编程中,我们常使用全局变量标志位对不同的函数进行通知,这种方式特别好理解。但是在多个文件中,有太多全局
不好管理,标志位多了管理会变得比较麻烦,实时性也不高。解决这个问题,我们使用线程间同步来解决这个问题。

一、信号量

信号量是在两个线程中进行同步的方式,就是发送信号的意思。比如简单的收发场景,线程1得到一个数据,需要通知线程2去发送这个数据。但是线程2需要等待线程1得到数据才能发送。

简单的例子

直观进行感受

最简单的信号量,线程1每次对变量+1便通知线程2发送

/*
 * 程序清单:最简单的信号量
 *
 * 线程1每次对变量+1便通知线程2发送
 * 
 */
#include 

#define THREAD_PRIORITY         25
#define THREAD_TIMESLICE        5

/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
static rt_uint8_t count = 0;

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{
  
    while(1)
    {
            count++;            
						rt_kprintf("thread1 sent count\n");			
            rt_sem_release(dynamic_sem);   
						rt_thread_mdelay(2000);
    }
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread2_entry(void *parameter)
{
    rt_err_t result;
    while(1)
    {
        /* 永久方式等待信号量,获取到信号量,则执行number自加的操作 */
        result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
        if (result != RT_EOK)
        {        
            rt_kprintf("t2 take a dynamic semaphore, failed.\n");
            rt_sem_delete(dynamic_sem);
            return;
        }
        else
        {      
						rt_kprintf("thread2 get count = %d\n",count);	                   
        }
    }   
}

/* 信号量示例的初始化 */
int semaphore_sample()
{
    /* 创建一个动态信号量,初始值是0 */
    dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO);
    if (dynamic_sem == RT_NULL)
    {
        rt_kprintf("create dynamic semaphore failed.\n");
        return -1;
    }
    else
    {
        rt_kprintf("create done. dynamic semaphore value = 0.\n");
    }

    rt_thread_init(&thread1,
                   "thread1",
                   rt_thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack), 
                   THREAD_PRIORITY-1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
                   
    rt_thread_init(&thread2,
                   "thread2",
                   rt_thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack), 
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);

运行结果:
RT-THREAD 内核快速入门(三) 信号量,互斥量,事件_第1张图片

可以看到线程1优先级比线程2要高,线程2想要运行必须处在就绪状态和线程1需要挂起。从输出的信息可以看见,线程2得不到信号量会挂起,直到线程1释放信号量,线程2处于就绪状态,等待被执行。线程1主动挂起,线程2运行。

同步信号运行规律

通过这个例子就知道一个非常重要的运行规律,理解好这个规律,后面的线程同步与通信就可以很快进行上手。这个规律就是这幅图,这幅图真的十分重要。这里我们用信号量对这系统运行状态图进行分析。

RT-THREAD 内核快速入门(三) 信号量,互斥量,事件_第2张图片

分析前先对之前线程进行一个简单的回顾:

回顾:
rt-thread的运行调度方式是基于优先级全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。

系统根据优先级进行运行,优先级高的得到优先执行权,同优先级按照时间片进行轮流执行。

分析:
分析使用刚刚上面的例子帮助大家理解,线程1优先级比线程2优先级要高,线程1只要不挂起,线程2就不能得到CPU的控制权,就得不到运行。在刚刚的例子中,线程1每次对变量+1便通知线程2发送。线程1先得到运行,之后线程释放信号量,可以简要的这样理解(不够准确,不影响使用),释放信号量之后,就使得线程2从挂起状态转到就绪状态,线程1便挂起。线程2得到控制权,从挂起的位置,也就是获取信号量的地方运行,打印变量。直到第二次运行,信号量值为0(初始化为0,线程1只是发送了一次信号量,使得信号量+1),线程2得不到信号量便挂起,直到下一次线程1释放信号量,线程2转变为就绪状态开始执行获取信号量。

还有一个需要注意的就是初始化的时候,是RT_IPC_FLAG_FIFO还是RT_IPC_FLAG_PRIO方式,这是在多个线程同一个获取信号量的场景,都是针对线程间排队的方式,影响谁先获取同步信号得到激活。

第二种好理解:RT_IPC_FLAG_PRIO方式
线程是通过排队的方式获取的,优先级高的先进行排队,优先级高的等待线程将先获得等待的信号量。当释放信号量的时候先,线程按照优先级排队,优先级高的获取信号量,转变为就绪状态,得到执行。

第一种:RT_IPC_FLAG_FIFO(先进先出方式)
当选择 RT_IPC_FLAG_FIFO,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;

推荐使用第一种方式:第一种是实时调度,更利于使用分析以及调试。

多线程获取同一信号量例程:

/*
 * 程序清单:多线程获取同一信号量情况
 *
 * RT_IPC_FLAG_PRIO 模式,线程1释放信号量,线程2和3获取信号量
 * 
 */
#include 

#define THREAD_PRIORITY         25
#define THREAD_TIMESLICE        5

/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
static rt_uint8_t count = 0;

ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{
  
    while(1)
    {
            count++;            
						rt_kprintf("thread1 sent count\n");			
            rt_sem_release(dynamic_sem);   
			      rt_sem_release(dynamic_sem);  
						rt_thread_mdelay(2000);
    }
}

ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;

static char thread3_stack[1024];
static struct rt_thread thread3;
static void rt_thread2_entry(void *parameter)
{
    rt_err_t result;
    while(1)
    {
        /* 永久方式等待信号量,获取到信号量,则执行number自加的操作 */
        result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
        if (result != RT_EOK)
        {        
            rt_kprintf("t2 take a dynamic semaphore, failed.\n");
            rt_sem_delete(dynamic_sem);
            return;
        }
        else
        {  
			rt_kprintf("thread2 get count = %d\n",count);
			rt_thread_mdelay(5);/*高优先级线程休眠,让低优先级线程运行*/	                   
        }
    }   
}


static void rt_thread3_entry(void *parameter)
{
    rt_err_t result;
    while(1)
    {
        /*低优先级得到信号量*/
        result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
        if (result != RT_EOK)
        {        
            rt_kprintf("t2 take a dynamic semaphore, failed.\n");
            rt_sem_delete(dynamic_sem);
            return;
        }
        else
        {      
			rt_kprintf("thread3 get count = %d\n",count);	                   
        }
    }   
}

/* 信号量示例的初始化 */
int semaphore_sample()
{
    /* 创建一个动态信号量,初始值是0 */
    dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);
    if (dynamic_sem == RT_NULL)
    {
        rt_kprintf("create dynamic semaphore failed.\n");
        return -1;
    }
    else
    {
        rt_kprintf("create done. dynamic semaphore value = 0.\n");
    }

    rt_thread_init(&thread1,
                   "thread1",
                   rt_thread1_entry,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack), 
                   THREAD_PRIORITY-1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
                   
    rt_thread_init(&thread2,
                   "thread2",
                   rt_thread2_entry,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack), 
                   THREAD_PRIORITY, THREAD_TIMESLICE);
									 
    rt_thread_startup(&thread2);

									 
									     rt_thread_init(&thread3,
                   "thread3",
                   rt_thread3_entry,
                   RT_NULL,
                   &thread3_stack[0],
                   sizeof(thread3_stack), 
                   THREAD_PRIORITY+1, THREAD_TIMESLICE);
    rt_thread_startup(&thread3);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);


RT-THREAD 内核快速入门(三) 信号量,互斥量,事件_第3张图片

信号量常用的运用场景

发送通知信号:
这里就比较广了,上面的简单例子就是,又比如传感器线程获取数据,之后通过信号量通知线程处理。

锁:
其实不常用,常用互斥量作为锁,很少用信号量,因为会产生优先级翻转的问题,稍后描述。可以通过锁对需要操作的数据上锁,当线程在访问变量的时候,操作这些锁住的变量(称为临界区)。

生产者,消费者问题:
解决间线程速度不匹配问题,其问题描述是生产与消费速度不匹配的问题。

这里给出一个较为复杂的例程:生产者消费者问题,理解好这个线程就大概能够使用了。

例程:这里使用RTT官方例程,自己结合上面的理解啦,这里就不详细阐述了。大概就是,生产线程会根据还有多少个位置进行产苹果,每次有一个空位,就成产一个苹果(随便啦)。同时还有在生存的时候防备消费者偷吃,因此在生产的时候对生产的苹果进行保护(上锁),在生产完毕之后通知消费者去吃。由于消费者吃的比较慢,生产比较快,因此会生产这会先填满空位,之后消费者就慢慢吃,直到直到打烊(消费者线程退出)。

/* 
 * Copyright (c) 2006-2018, RT-Thread Development Team 
 * 
 * SPDX-License-Identifier: Apache-2.0 
 * 
 * Change Logs: 
 * Date           Author       Notes 
 * 2018-08-24     yangjie      the first version 
 */  

/*
 * 程序清单:生产者消费者例子
 *
 * 这个例子中将创建两个线程用于实现生产者消费者问题
 *(1)生产者线程将cnt值每次加1并循环存入array数组的5个成员内;
 *(2)消费者线程将生产者中生产的数值打印出来,并累加求和
 */
#include 

#define THREAD_PRIORITY       6
#define THREAD_STACK_SIZE     512
#define THREAD_TIMESLICE      5

/* 定义最大5个元素能够被产生 */
#define MAXSEM 5

/* 用于放置生产的整数数组 */
rt_uint32_t array[MAXSEM];

/* 指向生产者、消费者在array数组中的读写位置 */
static rt_uint32_t set, get;

/* 指向线程控制块的指针 */
static rt_thread_t producer_tid = RT_NULL;
static rt_thread_t consumer_tid = RT_NULL;

struct rt_semaphore sem_lock;
struct rt_semaphore sem_empty, sem_full;

/* 生产者线程入口 */
void producer_thread_entry(void *parameter)
{
    int cnt = 0;

    /* 运行10次 */
    while (cnt < 10)
    {
        /* 获取一个空位 */
        rt_sem_take(&sem_empty, RT_WAITING_FOREVER);

        /* 修改array内容,上锁 */
        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        array[set % MAXSEM] = cnt + 1;
        rt_kprintf("the producer generates a number: %d\n", array[set % MAXSEM]);
        set++;
        rt_sem_release(&sem_lock);
       /* 修改array内容,解锁 */
			
        /* 发布一个满位 */
        rt_sem_release(&sem_full);
        cnt++;

        /* 暂停一段时间 */
        rt_thread_mdelay(20);
    }

    rt_kprintf("the producer exit!\n");
}

/* 消费者线程入口 */
void consumer_thread_entry(void *parameter)
{
    rt_uint32_t sum = 0;

    while (1)
    {
        /* 获取一个满位 */
        rt_sem_take(&sem_full, RT_WAITING_FOREVER);

        /* 临界区,上锁进行操作 */
        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        sum += array[get % MAXSEM];
        rt_kprintf("the consumer[%d] get a number: %d\n", (get % MAXSEM), array[get % MAXSEM]);
        get++;
        rt_sem_release(&sem_lock);

        /* 释放一个空位 */
        rt_sem_release(&sem_empty);

        /* 生产者生产到10个数目,停止,消费者线程相应停止 */
        if (get == 10) break;

        /* 暂停一小会时间 */
        rt_thread_mdelay(50);
    }

    rt_kprintf("the consumer sum is: %d\n", sum);
    rt_kprintf("the consumer exit!\n");
}

int producer_consumer(void)
{
    set = 0;
    get = 0;

    /* 初始化3个信号量 */
    rt_sem_init(&sem_lock, "lock",     1,      RT_IPC_FLAG_FIFO);
    rt_sem_init(&sem_empty, "empty",   MAXSEM, RT_IPC_FLAG_FIFO);
    rt_sem_init(&sem_full, "full",     0,      RT_IPC_FLAG_FIFO);

    /* 创建生产者线程 */
    producer_tid = rt_thread_create("producer",
                                    producer_thread_entry, RT_NULL,
                                    THREAD_STACK_SIZE,
                                    THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    if (producer_tid != RT_NULL)
    {
        rt_thread_startup(producer_tid);
    }
    else
    {
        rt_kprintf("create thread producer failed");
        return -1;
    }

    /* 创建消费者线程 */
    consumer_tid = rt_thread_create("consumer",
                                    consumer_thread_entry, RT_NULL,
                                    THREAD_STACK_SIZE,
                                    THREAD_PRIORITY + 1, THREAD_TIMESLICE);
    if (consumer_tid != RT_NULL)
    {
        rt_thread_startup(consumer_tid);
    }
    else
    {
        rt_kprintf("create thread consumer failed");
        return -1;
    }

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(producer_consumer, producer_consumer sample);

二、互斥量

互斥量使用和信号量使用起来方式几乎一样,只需要直到它的最常用的场景和他和信号量的区别,由于比较相似,仅对它们的不同做讨论。

联系

不同处:
信号量是多值信号,而互斥量是二值信号,只有0和1也就是说只能获取一次,在释放后只能获取一次。

使用场景:

临界锁

互斥量常用在临界锁,对多个线程需要访问的同一块数据,容易出现的优先级翻转的问题和数据不一致性和不完整性的问题。也就是虽然信号量也能够作为锁,但是会出现优先级翻转问题。

优先级翻转理解:
举一个比如了解优先级翻转的例子,有三个人A,B,C,代表三个线程,A优先级高是老大哥,B优先级中等是2哥(划水的),C优先级低是小弟。A,B,C需要一起干活组装一辆自行车,规则是只有C组装完A才能开始组装,B不干活,划水的。有一个场景:C在组装最后一步的时候,因为某些事情,被B打断了(C挂起了),等到B打断事情结束(B不访问临界区,划水的),到A组装了(A优先级高,A先运行),由于C没完成组装(临界区上锁,C正在访问临界区,正在组装自行车),A需要等待C完成才能组装(A因此挂起),终于又轮到C了,C完成(临界区解锁),A才能组装自行车,完成组装自行车的第一步。发现了吗?A优先级高反而得不到运行,C得到运行,组装效率下降了,老大哥A吃亏了。这就是优先级翻转的问题,如果记住规则就好理解了。

互斥锁:
互斥锁是解决优先级翻转的一个总要的方式(注意中断里面不能使用,等到后面篇章解释RTOS的线程调度的原理就知道了)

在刚刚的例子中,将信号量(临界锁)换成互斥量会变怎么样。继续用刚刚的例子进行解释,用了互斥量就相当于C在组装车的时候变成老大哥,它的优先级与老大哥优先级是一样的(A),这样。回到刚刚被B打断的时候,由于C在组装车的时候,相当于老大哥,优先级比B高,B不能打断C组装自行车,因此C能组装,A也能够顺利组装自行车,最后再到B划水。B不在组装自行车的时候捣乱,组装自行车的效率就高了,优先级也没翻转。

理解完上面的例子:让我们看代码实现,我就不对例程进行分析了,因为以及足够理解了。

例子:

#include 

/* 指向线程控制块的指针 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_thread_t tid3 = RT_NULL;
static rt_mutex_t mutex = RT_NULL;


#define THREAD_PRIORITY       10
#define THREAD_STACK_SIZE     512
#define THREAD_TIMESLICE    5

/* 线程 1 入口 */
static void thread1_entry(void *parameter)
{
    /* 先让低优先级线程运行 */
    rt_thread_mdelay(100);

    /* 此时 thread3 持有 mutex,并且 thread2 等待持有 mutex */

    /* 检查 thread2 与 thread3 的优先级情况 */
    if (tid2->current_priority != tid3->current_priority)
    {
        /* 优先级不相同,测试失败 */
        rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
        rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
        rt_kprintf("test failed.\n");
        return;
    }
    else
    {
        rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);
        rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);
        rt_kprintf("test OK.\n");
    }
}

/* 线程 2 入口 */
static void thread2_entry(void *parameter)
{
    rt_err_t result;

    rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);

    /* 先让低优先级线程运行 */
    rt_thread_mdelay(50);

    /*
     * 试图持有互斥锁,此时 thread3 持有,应把 thread3 的优先级提升
     * 到 thread2 相同的优先级
     */
    result = rt_mutex_take(mutex, RT_WAITING_FOREVER);

    if (result == RT_EOK)
    {
        /* 释放互斥锁 */
        rt_mutex_release(mutex);
    }
}

/* 线程 3 入口 */
static void thread3_entry(void *parameter)
{
    rt_tick_t tick;
    rt_err_t result;

    rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);

    result = rt_mutex_take(mutex, RT_WAITING_FOREVER);
    if (result != RT_EOK)
    {
        rt_kprintf("thread3 take a mutex, failed.\n");
    }

    /* 做一个长时间的循环,500ms */
    tick = rt_tick_get();
    while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ;

    rt_mutex_release(mutex);
}

int pri_inversion(void)
{
    /* 创建互斥锁 */
    mutex = rt_mutex_create("mutex", RT_IPC_FLAG_PRIO);
    if (mutex == RT_NULL)
    {
        rt_kprintf("create dynamic mutex failed.\n");
        return -1;
    }

    /* 创建线程 1 */
    tid1 = rt_thread_create("thread1",
                            thread1_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    if (tid1 != RT_NULL)
         rt_thread_startup(tid1);

    /* 创建线程 2 */
    tid2 = rt_thread_create("thread2",
                            thread2_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY, THREAD_TIMESLICE);
    if (tid2 != RT_NULL)
        rt_thread_startup(tid2);

    /* 创建线程 3 */
    tid3 = rt_thread_create("thread3",
                            thread3_entry,
                            RT_NULL,
                            THREAD_STACK_SIZE,
                            THREAD_PRIORITY + 1, THREAD_TIMESLICE);
    if (tid3 != RT_NULL)
        rt_thread_startup(tid3);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(pri_inversion, prio_inversion sample);

运行结果:

RT-THREAD 内核快速入门(三) 信号量,互斥量,事件_第4张图片

三、事件

事件比上面的信号量和互斥量都好理解,事件可以处理但事件和并发事件。若有一线程:A,A只有遇到事件c和d或者e才去处理。这里比较简单,仅有一点需要注意,事件发生多次只要不去处理,也仅作为一次。由于相对于比较简单,直接上例程,就不分析啦。

简单例子

例程:也是RTT官网的例程

/* 
 * Copyright (c) 2006-2018, RT-Thread Development Team 
 * 
 * SPDX-License-Identifier: Apache-2.0 
 * 
 * Change Logs: 
 * Date           Author       Notes 
 * 2018-08-24     yangjie      the first version 
 */ 

/*
 * 程序清单:事件例程
 *
 * 程序会初始化2个线程及初始化一个静态事件对象
 * 一个线程等待于事件对象上,以接收事件;
 * 一个线程发送事件 (事件3/事件5)
*/
#include 

#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;

/* 线程1入口函数 */
static void thread1_recv_event(void *param)
{
    rt_uint32_t e;

    /* 第一次接收事件,事件3或事件5任意一个可以触发线程1,接收完后清除事件标志 */
    if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),
                      RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
                      RT_WAITING_FOREVER, &e) == RT_EOK)
    {
        rt_kprintf("thread1: OR recv event 0x%x\n", e);
    }

    rt_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");
}


ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;

/* 线程2入口 */
static void thread2_send_event(void *param)
{
    rt_kprintf("thread2: send event3\n");
    rt_event_send(&event, EVENT_FLAG3);
    rt_thread_mdelay(200);

    rt_kprintf("thread2: send event5\n");
    rt_event_send(&event, EVENT_FLAG5);
    rt_thread_mdelay(200);

    rt_kprintf("thread2: send event3\n");
    rt_event_send(&event, EVENT_FLAG3);
    rt_kprintf("thread2 leave.\n");
}

int event_sample(void)
{
    rt_err_t result;

    /* 初始化事件对象 */
    result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO);
    if (result != RT_EOK)
    {
        rt_kprintf("init event failed.\n");
        return -1;
    }

    rt_thread_init(&thread1,
                   "thread1",
                   thread1_recv_event,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);

    rt_thread_init(&thread2,
                   "thread2",
                   thread2_send_event,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);

    return 0;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(event_sample, event sample);

结果:
RT-THREAD 内核快速入门(三) 信号量,互斥量,事件_第5张图片

四、线程同步的关系

总结它们之间的联系(异同),就能很快掌握后面的线程同步与通信,下面简单介绍。

联系

又是这张图,这张图,看多看两次都不为过,结合这样图总结一下它们直接的异同。

RT-THREAD 内核快速入门(三) 信号量,互斥量,事件_第6张图片
相同处:

  • 激活/挂起线程方式一样。相同出就是都是线程之间进行同步的一种方式,同步就是激活/挂起线程使得线程之间能够得到有序的运行。
  • 线程间的排队方式都可以设置为一样。
    发送信号量(rt_sem_release) OR 发送互斥量(rt_mutex_release)OR发送事件(rt_event_send),它们激活都是线程队列中的第一个符合条件的线程,而线程排队是通过RT_IPC_FLAG_PRIO或者RT_IPC_FLAG_FIFO进行排队的。先排队,然后根据条件激活线程。 推荐使用按优先级排队的方式。
  • 不在中断里面进行信号量或者互斥量或者事件的获取,一般只在中断进行这些同步信号的释放。

不同处:

  • 激活条件不同。信号量激活条件是最先排队的线程先激活,互斥量和信号量一样,唯一不同的就是,互斥量优先级会继承。事件根据事件设定的类型,对排队的线程进行激活。
  • 运用场景不同。信号量场景,通知信号,生产者消费者问题。互斥量,临界保护(锁)。事件,事件集或者单个事件
  • 它们在某些条件下能够互相替代使用。比如只有两个线程访问临界区,其他线程优先级都是比这两个访问线程的优先级要低的,可以使用信号量代替互斥量。如果没有数量要求,也可以使用事件代替信号量。

五、总结

最为核心就是理解好信号量,因为设计线程的运行状态的改变,理解好这个,其他的就可以进行迁移。其次就是注意使用场景了,不在中断获取,每个同步信号的大概使用场景,一定场景下它们能够互相替换,找到最合适当前场景的即可。

你可能感兴趣的:(RT-THREAD,系列入门,嵌入式,rtos,stm32,单片机)