开发者社区> 矜辰所致> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

RT-Thread记录(六、IPC机制之信号量、互斥量和事件集)

简介: 上文说到 RT-Thread 对临界区的处理方式有多种,其中已经分析了关闭调度器和屏蔽中断的方式, 本文就来学学另外的线程同步方式。
+关注继续查看
上文说到 RT-Thread 对临界区的处理方式有多种,其中已经分析了关闭调度器和屏蔽中断的方式,
本文就来学学另外的线程同步方式。

前言

在我们专栏前面的文章中,已经学习过 RT-Thread 线程操作函数、软件定时器、临界区的保护,我们都进行了一些底层的分析,能让我们更加理解 RT-Thread 的内核,但是也不要忽略了上层的函数使用 要理解 RT-Thread 面向对象的思想,对所有的这些线程啊,定时器,包括要介绍的信号量,邮箱这些,都是以 对象 来操作,直白的说来就是 对于所有这些对象,都是以结构体的形式来表示,然后通过对这个对象结构体的操作来进行的。

本文所要介绍的内容属于 IPC机制,这些内容相对来说比较简单,我们重点在于学会如何使用以及了解他们的使用场合。

本 RT-Thread 专栏记录的开发环境:
RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX开发快速上手)
RT-Thread记录(二、RT-Thread内核启动流程 — 启动文件和源码分析
RT-Thread 内核篇系列博文链接:
RT-Thread记录(三、RT-Thread 线程操作函数及线程管理与FreeRTOS的比较)
RT-Thread记录(四、RT-Thread 时钟节拍和软件定时器)
RT-Thread记录(五、RT-Thread 临界区保护)

一、IPC机制

在嵌入式操作系统中,运行代码主要包括线程 和 ISR,在他们的运行过程中,因为应用或者多线程模型带来的需求,有时候需要同步,有时候需要互斥,有时候也需要彼此交换数据。操作系统必须提供相应的机制来完成这些功能,这些机制统称为 线程间通信(IPC机制)。

本文所要介绍的就是关于线程同步的信号量、互斥量、事件 也属于 IPC机制。

RT-Thread 中的 IPC机制包括信号量、互斥量、事件、邮箱、消息队列。对于学习 RT-Thread ,这些IPC机制我们必须要学会灵活的使用。

为什么要说一下这个IPC机制?

我们前面说到过,RT-Thread 面向对象的思想,所有的这些 IPC 机制都被当成一个对象,都有一个结构体控制块,我们用信号量结构体来看一看:
在这里插入图片描述
Kernel object有哪些,我们可以从基础内核对象结构体定义下面的代码找到:
在这里插入图片描述
本节说明了 RT-Thread 的 IPC 机制,同时通过 信号量的结构体控制块再一次的认识了 RT-Thread 面向对象的设计思想。

在我的 FreeRTOS 专栏中,对于FreeRTOS 的信号量,互斥量,事件集做过说明和测试。在这个部分,实际上 RT-Thread 与 FreeRTOS 是类似的,都是一样的思想。所以如果属熟悉FreeRTOS的话,这部分是简单的,我们要做的就是记录一下 对象的控制块,和操作函数,加以简单的示例测试。

二、信号量

信号量官方的说明是:信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。

信号量非常灵活,可以使用的场合也很多:

  • 比如 一个典型的应用场合就是停车位模型,总共有多少个车位,就是多少个信号量,入口进入一辆车信号量-1,出口离开一辆车信号量+1。
  • 比如 两个线程之间的同步,信号量的值初始化成 0,而尝试获得该信号量的线程,一定需要等待另一个释放信号量的线程先执行完。

在 FreeRTOS 中存在二值信号量,但是 RT-Thread 中已经没有了,官方有说明:
在这里插入图片描述
信号量记住一句话基本就可以,释放一次信号量就+1,获取一次就-1,如果信号量数据为0,那么尝试获取的线程就会挂机,直到有线程释放信号量使得信号量大于0。

2.1 信号量控制块

老规矩用源码,解释看注释(使用起来也方便复制 ~ ~!):


#ifdef RT_USING_SEMAPHORE
/**
 * Semaphore structure
 * value 信号量的值,直接表明目前信号量的数量
 */
struct rt_semaphore
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint16_t          value;                         /**< value of semaphore. */
    rt_uint16_t          reserved;                      /**< reserved field */
};
/*
rt_sem_t 是指向 semaphore 结构体的指针类型
*/
typedef struct rt_semaphore *rt_sem_t;
#endif

2.2 信号量操作

2.2.1 创建和删除

同以前的线程那些一样,动态的方式,先定义一个信号量结构体的指针变量,接收创建好的句柄。

创建信号量:

/*
参数的含义:
1、name     信号量名称
2、value     信号量初始值
3、flag     信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回值:
信号量创建成功,返回信号量的控制块指针
信号量创建失败,返回RT_BULL 
*/
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)

对于最后的参数 flag,决定了当信号量不可用时(就是当信号量为0的时候),多个线程等待的排队方式。只有RT_IPC_FLAG_FIFO (先进先出)或 RT_IPC_FLAG_PRIO(优先级等待)两种 flag。

关于用哪一个,要看具体的情况,官方有特意说明:
在这里插入图片描述
删除信号量:

/*
参数:
sem     rt_sem_create() 创建的信号量对象,信号量句柄
返回值:
RT_EOK     删除成功
*/
rt_err_t rt_sem_delete(rt_sem_t sem)

2.2.2 初始化和脱离

静态的方式,先定义一个信号量结构体,然后对他进行初始化。

初始化信号量:

/**
参数的含义:
1、sem         信号量对象的句柄,就是开始定义的信号量结构体变量
2、name     信号量名称
3、value     信号量初始值
4、flag     信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回值:
RT_EOK     初始化成功
 */
rt_err_t rt_sem_init(rt_sem_t    sem,
                     const char *name,
                     rt_uint32_t value,
                     rt_uint8_t  flag)

脱离信号量:

/*
参数:
sem     信号量对象的句柄
返回值:
RT_EOK     脱离成功
*/
rt_err_t rt_sem_detach(rt_sem_t sem);

2.2.3 获取信号量

当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1。

/**
参数:
1、sem         信号量对象的句柄
2、time     指定的等待时间,单位是操作系统时钟节拍(OS Tick)
返回值:
RT_EOK             成功获得信号量
-RT_ETIMEOUT     超时依然未获得信号量
-RT_ERROR         其他错误
 */
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)

注意!要等待的时间是系统时钟节拍(OS Tick)。

无等待获取信号量

//就是上面获取的等待时间为0的方式
rt_err_t rt_sem_trytake(rt_sem_t sem)
{
    return rt_sem_take(sem, 0);
}

当线程申请的信号量资源实例为0时,直接返回 - RT_ETIMEOUT。

2.2.4 释放信号量

释放信号量可以使得该信号量+1,如果有线程在等待这个信号量,可以唤醒这个线程。

/**
参数:
sem     信号量对象的句柄
返回值:
RT_EOK     成功释放信号量
 */
rt_err_t rt_sem_release(rt_sem_t sem)

2.2.5 信号量控制

信号量控制函数,用来重置信号量,使得信号量恢复为设定的值:


/**
 * This function can get or set some extra attributions of a semaphore object.
参数:
sem     信号量对象的句柄
cmd    信号量控制命令 ,支持命令:RT_IPC_CMD_RESET 
arg    暂时不知道
返回值:
RT_EOK     成功释放信号量

 */
rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg)
{
    rt_ubase_t level;

    /* parameter check */
    RT_ASSERT(sem != RT_NULL);
    RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);

    if (cmd == RT_IPC_CMD_RESET)
    {
        rt_ubase_t value;

        /* get value */
        value = (rt_ubase_t)arg;
        /* disable interrupt */
        level = rt_hw_interrupt_disable();

        /* resume all waiting thread */
        rt_ipc_list_resume_all(&sem->parent.suspend_thread);

        /* set new value */
        sem->value = (rt_uint16_t)value;

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        rt_schedule();

        return RT_EOK;
    }

    return -RT_ERROR;
}

使用示例:

rt_err_t result;
rt_uint32_t value;

value = 10; /* 重置的值,即重置为10 */
result = rt_sem_control(sem, RT_IPC_CMD_RESET, (void*)value)

/* 重置为0 */
rt_sem_control(sem, RT_IPC_CMD_RESET, RT_NULL)

对sem重置后,会先把sem上挂起的所有任务进行唤醒(任务的error是-RT_ERROR),然后把sem的值会重新初始化成设定的值。

在官方论坛有如下说明:
rt_sem_release后使用rt_sem_control的目的是因为在某些应用中必须rt_sem_takert_sem_release依次出现,而不允许rt_sem_release被连续多次调用,一旦出现这种情况会被认为是出现了异常,通过调用rt_sem_control接口来重新初始化 sem_ack恢复异常。

2.3 示例(典型停车场模型)

前面说到过,信号量非常灵活,可以使用的场合也很多,官方也有很多例子,我们这里做个典型的示例
— 停车场模型(前面用截图做解释,后面会附带源码)。

示例中,我们使用两个不同的按键来模拟车辆的进出,但是考虑到我们还没有学设备和驱动,没有添加按键驱动,所以我们用古老的方式来实现按键操作:

按键key3,代表车辆离开:
在这里插入图片描述
按键key2,代表车辆进入:
在这里插入图片描述
信号量的创建,初始10个车位:
在这里插入图片描述

当然不要忘了,车辆进入和车辆离开(两个按键)是需要两个线程的。

我们来看看测试效果,说明如图:
在这里插入图片描述
注意上图测试最后的细节,虽然 one car get out! 但是打印出来的停车位还是0,可以这么理解,key3_thread_entry线程释放了信号量以后还没来得及打印,等待信号量的线程key2_thread_entry就获取到了信号量。

具体的分析需要看rt_sem_release函数源码,里面会判断是否需要值+1,以及是否需要调度:
在这里插入图片描述
附上上面测试代码:

/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-02-16     RT-Thread    first version
 */

#include <rtthread.h>
#include "main.h"
#include "usart.h"
#include "gpio.h"

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

static struct rt_thread led1_thread;    //led1线程
static char led1_thread_stack[256];

static rt_thread_t led2_thread = RT_NULL; //led2线程

static rt_thread_t key2_thread = RT_NULL; //

static rt_thread_t key3_thread = RT_NULL; //

rt_sem_t mysem;


static void led1_thread_entry(void *par){
    while(1){
        LED1_ON;
        rt_thread_mdelay(1000);
        LED1_OFF;
        rt_thread_mdelay(1000);
    }
}

static void led2_thread_entry(void *par){
    while(1){
        LED2_ON;
        rt_thread_mdelay(500);
        LED2_OFF;
        rt_thread_mdelay(500);
    }
}

static void key2_thread_entry(void *par){
    static rt_err_t result;
    while(1){
        if(key2_read == 0){
            rt_thread_mdelay(10); //去抖动
            if(key2_read == 0){
                result = rt_sem_take(mysem, 1000);
                if (result != RT_EOK)
                {
                    rt_kprintf("the is no parking spaces now...\r\n");
                }
                else
                {
                    rt_kprintf("one car get in!,we have %d parking spaces now...\r\n",mysem->value);
                }
                while(key2_read == 0){rt_thread_mdelay(10);}
            }
        }
        rt_thread_mdelay(1);
    }
}

static void key3_thread_entry(void *par){
    while(1){
        if(key3_read == 0){
            rt_thread_mdelay(10); //去抖动
            if(key3_read == 0){
                if(mysem->value < 10){
                    rt_sem_release(mysem);
                    rt_kprintf("one car get out!,we have %d parking spaces now...\r\n",mysem->value);
                }
                while(key3_read == 0){rt_thread_mdelay(10);} //去抖动
            }
        }
        rt_thread_mdelay(1);
    }
}
int main(void)
{
    MX_GPIO_Init();
    MX_USART1_UART_Init();


    rt_err_t rst2;
    rst2 = rt_thread_init(&led1_thread,
                        "led1_blink ",
                        led1_thread_entry,
                        RT_NULL,
                        &led1_thread_stack[0],
                        sizeof(led1_thread_stack),
                        RT_THREAD_PRIORITY_MAX -1,
                        50);

    if(rst2 == RT_EOK){
        rt_thread_startup(&led1_thread);
    }


    mysem = rt_sem_create("my_sem1", 10, RT_IPC_FLAG_FIFO);
    if(RT_NULL == mysem){
        LOG_E("create sem failed!...\n");
    }
    else LOG_D("we have 10 parking spaces now...\n");

    key2_thread = rt_thread_create("key2_control",
                                key2_thread_entry,
                                RT_NULL,
                                512,
                                RT_THREAD_PRIORITY_MAX -2,
                                50);

        /* 如果获得线程控制块,启动这个线程 */
        if (key2_thread != RT_NULL)
            rt_thread_startup(key2_thread);

     key3_thread = rt_thread_create("key3_control",
                                key3_thread_entry,
                                RT_NULL,
                                512,
                                RT_THREAD_PRIORITY_MAX -2,
                                50);

        /* 如果获得线程控制块,启动这个线程 */
        if (key3_thread != RT_NULL)
            rt_thread_startup(key3_thread);
    return RT_EOK;
}


void led2_Blink(){
    led2_thread = rt_thread_create("led2_blink",
                            led2_thread_entry,
                            RT_NULL,
                            256,
                            RT_THREAD_PRIORITY_MAX -1,
                            50);

    /* 如果获得线程控制块,启动这个线程 */
    if (led2_thread != RT_NULL)
        rt_thread_startup(led2_thread);
}

MSH_CMD_EXPORT(led2_Blink, Led2 sample);

三、互斥量

互斥量是一种特殊的二值信号量。互斥量的状态只有两种,开锁或闭锁(两种状态值)。

互斥量支持递归,持有该互斥量的线程也能够再次获得这个锁而不被挂起。自己能够再次获得互斥量。

互斥量可以解决优先级翻转问题,它能够实现优先级继承。

互斥量互斥量不能在中断服务例程中使用。

3.1 优先级翻转

优先级翻转,我在以前 FreeRTOS 专栏写过:
在这里插入图片描述
再用官方的图加深理解:
在这里插入图片描述

3.2 优先级继承

优先级继承,我在以前 FreeRTOS 专栏也写过:
在这里插入图片描述
再用官方的图加深理解:
在这里插入图片描述
需要切记的是互斥量不能在中断服务例程中使用。

3.3 互斥量控制块

#ifdef RT_USING_MUTEX
/**
 * Mutual exclusion (mutex) structure
 * parent                 继承ipc类
 * value                 互斥量的值
 * original_priority     持有线程的原始优先级
 * hold                 持有线程的持有次数,可以多次获得
 * *owner                当前拥有互斥量的线程
 */
struct rt_mutex
{
    struct rt_ipc_object parent;              /**< inherit from ipc_object */
    rt_uint16_t          value;                /**< value of mutex */
    rt_uint8_t           original_priority;    /**< priority of last thread hold the mutex */
    rt_uint8_t           hold;                 /**< numbers of thread hold the mutex */

    struct rt_thread    *owner;               /**< current owner of mutex */
};
/* rt_mutext_t 为指向互斥量结构体的指针类型  */
typedef struct rt_mutex *rt_mutex_t;
#endif

3.4 互斥量操作

3.4.1 创建和删除

先定义一个指向互斥量结构体的指针变量,接收创建好的句柄。

创建互斥量:

/**
参数的含义:
1、name     互斥量名称
2、flag     该标志已经作废,无论用户选择 RT_IPC_FLAG_PRIO 还是 RT_IPC_FLAG_FIFO,
            内核均按照 RT_IPC_FLAG_PRIO 处理
返回值:
互斥量创建成功,返回互斥量的控制块指针
互斥量创建失败,返回RT_BULL 
 */
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)

删除互斥量:

/**
参数:
mutex    互斥量对象的句柄
返回值:
RT_EOK     删除成
 */
rt_err_t rt_mutex_delete(rt_mutex_t mutex)

3.4.2 初始化和脱离

静态的方式,先定义一个互斥量结构体,然后对他进行初始化。

初始化互斥量:

/**
参数的含义:
1、mutex 互斥量对象的句柄,指向互斥量对象的内存块,开始定义的结构体
2、name     互斥量名称
3、flag     该标志已经作废,按照 RT_IPC_FLAG_PRIO (优先级)处理
返回值:
RT_EOK     初始化成功
 */
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)

脱离互斥量:

/**
参数:
mutex    互斥量对象的句柄
返回值:
RT_EOK     成功
 */
rt_err_t rt_mutex_detach(rt_mutex_t mutex)

3.4.3 获取互斥量

一个时刻一个互斥量只能被一个线程持有。

如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。

/**
参数:
1、mutex    互斥量对象的句柄
2、time     指定的等待时间,单位是操作系统时钟节拍(OS Tick)
返回值:
RT_EOK             成功获得互斥量
-RT_ETIMEOUT     超时依然未获得互斥量
-RT_ERROR         获取失败
 */
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)

3.4.4 释放互斥量

在获得互斥量后,应该尽可能的快释放互斥量。

/**
参数:
mutex     互斥量对象的句
返回值:
RT_EOK     成功
 */
rt_err_t rt_mutex_release(rt_mutex_t mutex)

3.5 示例(优先级继承)

互斥量做一个简单的示例,但是即便简单,也能体现出优先级继承这个机制。

示例中,我们使用两个按键,key2按键,按一次获取互斥量,再按一次释放互斥量,打印自己初始优先级,当前优先级,互斥量占有线程优先级这几个量。key3按键,按一次,获取互斥量,立马就释放,也打印几个优先级。

互斥量的创建,和两个线程的优先级:
在这里插入图片描述
key2操作:
在这里插入图片描述

key3操作:
在这里插入图片描述
测试结果说明图:
在这里插入图片描述
示例中为了更好的演示并没有快进快出,实际使用还是需要快进快出,除非你自己就是有这种特出需求。

还有一个细节,就是 RT-Thread 中对象的 名字,只能显示8个字符长度,长了会截断,并不影响使用。

四、事件集

事件集这部分与 FreeRTOS 基本一样。

事件集主要用于线程间的同步,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。

RT-Thread 定义的事件集有以下特点:

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

4.1 事件集控制块

#ifdef RT_USING_EVENT
/**
 * flag defintions in event
 * 逻辑与
 * 逻辑或
 * 清除标志位
 */
#define RT_EVENT_FLAG_AND               0x01            /**< logic and */
#define RT_EVENT_FLAG_OR                0x02            /**< logic or */
#define RT_EVENT_FLAG_CLEAR             0x04            /**< clear flag */

/*
 * event structure
 * set:事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生
 */
struct rt_event
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint32_t          set;                           /**< event set */
};
/* rt_event_t 是指向事件结构体的指针类型  */
typedef struct rt_event *rt_event_t;
#endif

4.2 事件集操作

4.2.1 创建和删除

先定义一个指向事件集结构体的指针变量,接收创建好的句柄。

创建事件集:

/**
参数的含义:
1、name     事件集的名称
2、flag     事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO理
返回值:
事件集创建成功,返回事件集的控制块指针
事件集创建失败,返回RT_BULL 
 */
rt_event_t rt_event_create(const char *name, rt_uint8_t flag)

flag 使用哪一个,解释和信号量一样,可参考信号量创建部分说明。

删除事件集:

/**
参数:
event    事件集对象的句柄
返回值:
RT_EOK     成功
 */
rt_err_t rt_event_delete(rt_event_t event)

4.2.2 初始化和脱离

静态的方式,先定义一个事件集结构体,然后对他进行初始化。

初始化事件集:

/**
参数的含义:
1、event    事件集对象的句柄
2、name     事件集的名称
3、flag     事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
返回值:
RT_EOK     初始化成功
 */
rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)

脱离事件集:

/**
参数:
event    事件集对象的句柄
返回值:
RT_EOK     成功
 */
rt_err_t rt_event_detach(rt_event_t event)

4.2.3 发送事件

发送事件函数可以发送事件集中的一个或多个事件。

/**
参数的含义:
1、event    事件集对象的句柄
2、set        发送的一个或多个事件的标志值
返回值:
RT_EOK     成功
 */
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)

4.2.4 接收事件

内核使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或“逻辑或”来选择如何激活线程。

/**
参数的含义:
1、event        事件集对象的句柄
2、set            接收线程感的事件
3、option         接收选项,可取的值为
#define RT_EVENT_FLAG_AND               0x01       逻辑与    
#define RT_EVENT_FLAG_OR                0x02       逻辑或    
#define RT_EVENT_FLAG_CLEAR             0x04     选择清除重置事件标志位       
4、timeout        指定超时时间
5、recved        指向接收到的事件,如果不在意,可以使用 NULL
返回值:
RT_EOK             成功
-RT_ETIMEOUT     超时
-RT_ERROR         错误
 */
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)

4.3 示例(逻辑与和逻辑或)

事件集通过示例可以很好的理解怎么使用,我们示例中,用按钮发送事件,其他线程接收事件,进行对应的处理。

按键操作:
在这里插入图片描述
线程逻辑或处理:
在这里插入图片描述
逻辑或测试结果:
在这里插入图片描述
线程逻辑与处理:
在这里插入图片描述
逻辑与测试结果:
在这里插入图片描述

结语

前面说过,RT-Thread 的这些机制与 FreeRTOS 大体上类似,如果对 FreeRTOS 这部分感兴趣的,可以看一下 FreeRTOS 这部分的博文:
FreeRTOS记录(七、FreeRTOS信号量、事件标志组、邮箱和消息队列、任务通知的关系)

本文虽然只是介绍了信号量、互斥量和事件集这几个比较简单的线程同步操作,但是最终完成了后发现内容还是很多的。

洋洋洒洒这么多字,最终看下来自己还是挺满意的,希望我把该表述的都表达清楚了,希望大家多多提意见,让博主能给大家带来更好的文章。

那么下一篇的 RT-Thread 记录,就要来说说与线程通讯 有关的 邮箱、消息队列和信号内容了。

谢谢!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
[模板 辛普森积分] Grazed Grains | NCPC2021 | 辛普森积分求圆的面积并
题目描述 This year, there have been unusually many UFO sightings reported. Nobody knows if they are caused by optical illusions, weather phenomena, or secret technology being tested by foreign powers (terrestrial or not). UFO enthusiasts across the world rejoice, and speculations run wild.
28 0
Java——多线程高并发系列之创建多线程的四种方式(Thread、Runnable、Callable、线程池)
Java——多线程高并发系列之创建多线程的四种方式(Thread、Runnable、Callable、线程池)
48 0
java是如何解决单线程之间的通信问题呢?这篇文章给你答案
人与人之间通过交流构成了这个丰富多彩的世界,在计算机中,通过即时通信工具传递信息为我么的生活增添了很多乐趣也提供了很多遍历,而在java线程的世界里,线程之间的通信,可以极大的增强我们的功能,今天就带你一块走进线程通信的世界里。 这篇文章是基础入门文章,主要是wait和notify来解决单线程通信问题的。对于多线程通信极其实现方式我会在后续的课程中依次推出。
24 0
4.12【Java学习路线】Java语言基础自测考试 - 初级难度
4.12【Java学习路线】Java语言基础自测考试 - 初级难度
55 0
ESC搭建L4D2服务器
最近和朋友一起爽玩L4D2的三方图,结果游戏本身的本地联机延迟高到无法畅玩,大厅连接到的第三方服务器也多是魔改服,于是干脆自己使用阿里云ESC搭建一个。
223 0
蚂蚁金服硅谷ATEC科技大会:看技术如何带来平等的机会
人工智能、区块链等前沿技术将为金融科技带来何种变革?
2291 0
《像计算机科学家一样思考Python(第2版)》——第2章 变量、表达式和语句 2.1 赋值语句
本节书摘来自异步社区《像计算机科学家一样思考Python(第2版)》一书中的第2章,第2.1节,作者[美] Allen B. Downey,赵普明 译,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1440 0
C++MFC编程笔记day02 MFC消息映射机制、菜单资源使用
机制3:MFC消息映射机制: 类内声明,类外定义宏,绑定消息处理函数派生自CCmdTarget类内声明宏:DECLARE_MESSAGE_MAP()类外添加实现宏:BEGIN_MESSAGE_MAP(类名,父类名)END_MESSAGE_MAP...
1033 0
Mfc资源消息的响应机制
Mfc消息的响应机制   Mfc中有很多资源,如图标资源,菜单资源,工具栏资源等等;那么,资源是如何进行消息响应和消息映射的呢? 它们的流程是: 某种资源——对应的ID号——消息映射——响应函数的声明与实现 下面我们以工具栏的资源响应为例:(多文档的应用程序) 1.
885 0
+关注
矜辰所致
不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...
63
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载