RT-Thread 线程间同步【信号量、互斥量、事件集】

线程间同步

  • 一、信号量
    • 1. 创建信号量
    • 2. 获取信号量
    • 3. 释放信号量
    • 4. 删除信号量
    • 5. 代码示例
  • 二、互斥量
    • 1. 创建互斥量
    • 2. 获取互斥量
    • 3. 释放互斥量
    • 4. 删除互斥量
    • 5. 代码示例
  • 三、事件集
    • 1. 创建事件集
    • 2. 发送事件
    • 3. 接收事件
    • 4. 删除事件集
    • 5. 代码示例


简单来说,同步就是多个线程同时访问一块内存,好比如 一个线程向指定内存中写入一个数据,另一个线程就从该内存中读取数据,这就是“同步”。
在这里插入图片描述
线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行

(以下都以动态创建方式介绍)

一、信号量

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

每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。

1. 创建信号量

当创建一个信号量时,内核首先创建一个信号量控制块,然后对该控制块进行基本的初始化工作

 rt_sem_t rt_sem_create(const char *name,rt_uint32_t value,rt_uint8_t flag);

在这里插入图片描述
注: RT_IPC_FLAG_FIFO(先进先出)方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当选择 RT_IPC_FLAG_PRIO(优先级等待)方式时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。。

2. 获取信号量

线程通过获取信号量来获得信号量资源实例,当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1

rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);

在这里插入图片描述

在调用这个函数时,如果信号量的值等于零,那么说明当前信号量资源实例不可用,申请该信号量的线程将根据 time 参数的情况选择直接返回、或挂起等待一段时间、或永久等待,直到其他线程或中断释放该信号量。如果在参数 time 指定的时间内依然得不到信号量,线程将超时返回,返回值是 - RT_ETIMEOUT。

3. 释放信号量

释放信号量可以唤醒挂起在该信号量上的线程

rt_err_t rt_sem_release(rt_sem_t sem);

在这里插入图片描述

当信号量的值等于零时,并且有线程等待这个信号量时,释放信号量将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量;否则将把信号量的值加 1

4. 删除信号量

系统不再使用信号量时,可通过删除信号量以释放系统资源,适用于动态创建的信号量

rt_err_t rt_sem_delete(rt_sem_t sem);

在这里插入图片描述

调用这个函数时,系统将删除这个信号量。如果删除该信号量时,有线程正在等待该信号量,那么删除操作会先唤醒等待在该信号量上的线程(等待线程的返回值是 - RT_ERROR),然后再释放信号量的内存资源

5. 代码示例

这是一个信号量使用例程,该例程创建了一个动态信号量,初始化两个线程,一个线程发送信号量,一个线程接收到信号量后,执行相应的操作

#include <rtthread.h>#define THREAD_PRIORITY         25
#define THREAD_TIMESLICE        5/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{static rt_uint8_t count = 0;while(1){if(count <= 100){count++;}elsereturn;/* count 每计数 10 次,就释放一次信号量 */if(0 == (count % 10)){rt_kprintf("t1 release a dynamic semaphore.\n");rt_sem_release(dynamic_sem);}}
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread2_entry(void *parameter)
{static rt_err_t result;static rt_uint8_t number = 0;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{number++;rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number);}}
}/* 信号量示例的初始化 */
int semaphore_sample(void)
{/* 创建一个动态信号量,初始值是 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, THREAD_TIMESLICE);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",rt_thread2_entry,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY-1, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);

运行结果如下:

 \ | /
- RT -     Thread Operating System/ | \     3.1.0 build Aug 27 20182006 - 2018 Copyright by rt-thread team
msh >semaphore_sample
create done. dynamic semaphore value = 0.
msh >t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 1
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 2
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 3
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 4
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 5
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 6
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 7
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 8
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 9
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 10

二、互斥量

互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。互斥量类似于只有一个车位的停车场:当有一辆车进入的时候,将停车场大门锁住,其他车辆在外面等候。当里面的车出来时,将停车场大门打开,下一辆车才可以进入。

互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。

互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起

RT-Thread 操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承协议 (Sha, 1990)。优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占,如下图所示。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

1. 创建互斥量

创建一个互斥量时,内核首先创建一个互斥量控制块,然后完成对该控制块的初始化工作

rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);

在这里插入图片描述
注: 互斥量的 flag 标志已经作废,无论用户选择 RT_IPC_FLAG_PRIO 还是 RT_IPC_FLAG_FIFO,内核均按照 RT_IPC_FLAG_PRIO 处理

2. 获取互斥量

线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);

在这里插入图片描述

如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间

3. 释放互斥量

当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量

rt_err_t rt_mutex_release(rt_mutex_t mutex);

在这里插入图片描述

使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级

4. 删除互斥量

当不再使用互斥量时,通过删除互斥量以释放系统资源,适用于动态创建的互斥量

rt_err_t rt_mutex_delete (rt_mutex_t mutex);

在这里插入图片描述
当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是 - RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间

5. 代码示例

这是一个互斥量的应用例程,互斥锁是一种保护共享资源的方法。当一个线程拥有互斥锁的时候,可以保护共享资源不被其他线程破坏。下面用一个例子来说明,有两个线程:线程 1 和线程 2,线程 1 对 2 个 number 分别进行加 1 操作;线程 2 也对 2 个 number 分别进行加 1 操作,使用互斥量保证线程改变 2 个 number 值的操作不被打断

互斥量例程

#include <rtthread.h>#define THREAD_PRIORITY         8
#define THREAD_TIMESLICE        5/* 指向互斥量的指针 */
static rt_mutex_t dynamic_mutex = RT_NULL;
static rt_uint8_t number1,number2 = 0;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread_entry1(void *parameter)
{while(1){/* 线程 1 获取到互斥量后,先后对 number1、number2 进行加 1 操作,然后释放互斥量 */rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);number1++;rt_thread_mdelay(10);number2++;rt_mutex_release(dynamic_mutex);}
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread_entry2(void *parameter)
{while(1){/* 线程 2 获取到互斥量后,检查 number1、number2 的值是否相同,相同则表示 mutex 起到了锁的作用 */rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);if(number1 != number2){rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2);}else{rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1);}number1++;number2++;rt_mutex_release(dynamic_mutex);if(number1>=50)return;}
}/* 互斥量示例的初始化 */
int mutex_sample(void)
{/* 创建一个动态互斥量 */dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO);if (dynamic_mutex == RT_NULL){rt_kprintf("create dynamic mutex failed.\n");return -1;}rt_thread_init(&thread1,"thread1",rt_thread_entry1,RT_NULL,&thread1_stack[0],sizeof(thread1_stack),THREAD_PRIORITY, THREAD_TIMESLICE);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",rt_thread_entry2,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY-1, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}/* 导出到 MSH 命令列表中 */
MSH_CMD_EXPORT(mutex_sample, mutex sample);

运行结果如下:

 \ | /
- RT -     Thread Operating System/ | \     3.1.0 build Aug 24 20182006 - 2018 Copyright by rt-thread team
msh >mutex_sample
msh >mutex protect ,number1 = mumber2 is 1
mutex protect ,number1 = mumber2 is 2
mutex protect ,number1 = mumber2 is 3
mutex protect ,number1 = mumber2 is 4
…
mutex protect ,number1 = mumber2 is 48
mutex protect ,number1 = mumber2 is 49

防止优先级翻转特性例程

#include <rtthread.h>/* 指向线程控制块的指针 */
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 Operating System/ | \     3.1.0 build Aug 27 20182006 - 2018 Copyright by rt-thread team
msh >pri_inversion
the priority of thread2 is: 10
the priority of thread3 is: 11
the priority of thread2 is: 10
the priority of thread3 is: 10
test OK.

三、事件集

事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。

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

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

在 RT-Thread 中,每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。
在这里插入图片描述
线程 #1 的事件标志中第 1 位和第 30 位被置位,如果事件信息标记位设为逻辑与,则表示线程 #1 只有在事件 1 和事件 30 都发生以后才会被触发唤醒,如果事件信息标记位设为逻辑或,则事件 1 或事件 30 中的任意一个发生都会触发唤醒线程 #1。如果信息标记同时设置了清除标记位,则当线程 #1 唤醒后将主动把事件 1 和事件 30 清为零,否则事件标志将依然存在(即置 1)

1. 创建事件集

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

rt_event_t rt_event_create(const char* name, rt_uint8_t flag);

在这里插入图片描述
调用该函数接口时,系统会从对象管理器中分配事件集对象,并初始化这个对象,然后初始化父类 IPC 对象

2. 发送事件

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

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);

在这里插入图片描述

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

3. 接收事件

内核使用 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
在这里插入图片描述
option 的值可取:

/* 选择 逻辑与 或 逻辑或 的方式接收事件 */
RT_EVENT_FLAG_OR
RT_EVENT_FLAG_AND/* 选择清除重置事件标志位 */
RT_EVENT_FLAG_CLEAR

4. 删除事件集

系统不再使用 rt_event_create() 创建的事件集对象时,通过删除事件集对象控制块来释放系统资源

rt_err_t rt_event_delete(rt_event_t event);

在这里插入图片描述

5. 代码示例

这是事件集的应用例程,例子中初始化了一个事件集,两个线程。一个线程等待自己关心的事件发生,另外一个线程发送事件

#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;/* 线程 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_PRIO);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 Operating System/ | \     3.1.0 build Aug 24 20182006 - 2018 Copyright by rt-thread team
msh >event_sample
thread2: send event3
thread1: OR recv event 0x8
thread1: delay 1s to prepare the second event
msh >thread2: send event5
thread2: send event3
thread2 leave.
thread1: AND recv event 0x28
thread1 leave.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/161965.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PDF转成图片

使用开源库Apache PDFBox将PDF转换为图片 依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.4</version> </dependency> <dependency><groupId>org.apache…

DockerHub 无法访问 - 解决办法

背景 DockerHub 镜像仓库地址 https://hub.docker.com/ 突然就无法访问了,且截至今日(2023/11)还无法访问。 这对我们来说,还是有一些影响的: ● 虽然 DockerHub 页面无法访问,但是还是可以下载镜像的,只是比较慢而已 ● 没法通过界面查询相关镜像,或者维护相关镜像了…

JAVA 使用stream流将List中的对象某一属性创建新的List

JAVA 使用stream流将List中的对象某一属性创建新的List 1.stream流介绍 Java Stream是Java 8引入的一种新机制&#xff0c;它可以让我们以声明式方式操作集合数据&#xff0c;提供了更加简洁、优雅的集合处理方式。Stream是一个来自数据源的元素队列&#xff0c;并支持聚合操…

【Rxjava详解】(二) 操作符的妙用

文章目录 接口变化操作符mapflatmapdebouncethrottleFirst()takeconcat RxJava 是一个基于 观察者模式的异步编程库&#xff0c;它提供了丰富的操作符来处理和转换数据流。 操作符是 RxJava 的核心组成部分&#xff0c;它们提供了一种灵活、可组合的方式来处理数据流&#xf…

C++二分算法:得到子序列的最少操作次数

本文涉及的基础知识点 二分查找算法合集 题目 给你一个数组 target &#xff0c;包含若干 互不相同 的整数&#xff0c;以及另一个整数数组 arr &#xff0c;arr 可能 包含重复元素。 每一次操作中&#xff0c;你可以在 arr 的任意位置插入任一整数。比方说&#xff0c;如果…

【如何学习Python自动化测试】—— 多层窗口定位

6 、 多层窗口定位 多层窗口指的是在操作系统图形界面中&#xff0c;一个窗口被另一个窗口覆盖的情况。在多层窗口中&#xff0c;如何定位需要操作的窗口&#xff1f; 一种常见的方法是使用操作系统提供的AltTab快捷键&#xff0c;可以在打开的所有窗口中快速切换焦点。如果需要…

第十三章 控制值的转换 - 处理UTC时区指示符

文章目录 第十三章 控制值的转换 - 处理UTC时区指示符 第十三章 控制值的转换 - 处理UTC时区指示符 对于支持XML的类&#xff0c;可以指定在从XML文档导入时是否使用UTC时区指示符。同样&#xff0c;可以指定是否在导出时包含UTC时区指示符。 为此&#xff0c;指定XMLTIMEZON…

GEE生物量碳储量——利用sens和MK检验方法计算1987-2022年森林地上生物量AGB和碳储量的时空变化特征

简介: 本文是将之前已经处理好的森林生物量和碳储量数据保存到GEE Assets中,然后分别将单张影像导入到代码编辑器中,构建一个时间序列集合,并且这里需要用到的是我们给影像添加指定的时间属性,这样方便进行下一步的时序分析和空间预测。 首先,需要收集1987年至2022年期…

C语言实现Linux下TCP Server测试工具

Linux TCP Server测试工具代码 实现了接受数据并输出文本和十六制字符串 #include <stdio.h> #include<string.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <signal.h> #include <arpa/inet.h> #incl…

STM32内存介绍

ROM是一种只读存储器&#xff0c;经历了从NOR Flash到NAND Flash再到现在的eMMC的发展。为了便于使用和大批量生产&#xff0c;ROM进一步分为了4种类型&#xff1a;PROM、EPROM、EEPROM和Flash。PROM只能被编程一次&#xff0c;EPROM可擦写可编程且可达1000次&#xff0c;EEPRO…

leetcode/hot100

文章目录 一、哈希1.两数之和2.字母异位词分组3.最长连续序列 二、双指针4. 移动零5.盛最多水的容器6.三数之和7.接雨水 三、滑动窗口8.无重复字符的最长子串9.找到字符串中所有字母异位词 四、子串10.和为 K 的子数组 一、哈希 1.两数之和 1. 两数之和 class Solution { pu…

规则引擎Drools使用,0基础入门规则引擎Drools(二)高级语法

文章目录 系列文章索引五、规则属性1、enabled属性2、dialect属性3、salience属性4、no-loop属性5、activation-group属性6、agenda-group属性7、auto-focus属性8、timer属性9、date-effective属性10、date-expires属性 六、Drools高级语法1、global全局变量2、query查询3、fun…

20231122给RK3399的挖掘机开发板适配Android12

20231122给RK3399的挖掘机开发板适配Android12 2023/11/22 9:30 主要步骤&#xff1a; rootrootrootroot-X99-Turbo:~$ tar --use-compress-programpigz -xvpf rk356x_android12_220722.tgz rootrootrootroot-X99-Turbo:~$ cd rk_android12_220722/ rootrootrootroot-X99-Tur…

rk3568 适配以太网(mac 2 mac)

rk3568 适配以太网(mac 2 mac) MDI(Media Dependent Interface)是以太网中的一种接口标准,用于连接物理层(PHY)和数据链路层(MAC)之间的传输介质。 在以太网中,MDI通常通过RJ-45插座来实现,用于连接网线和网络设备。MDI接口提供了电气和机械特性,使得PHY和MAC能够正…

Python通过串口收发文件

单位内外网是隔离的,USB对拷线被禁用,安全优盘使用太费事,就想到了通过串口传输文件. import serial from xmodem import XMODEM import osdef Send_File(filepath, portCOM8, baudrate115200):bn os.path.basename(filepath)filesize os.stat(filepath).st_sizestrSendFile…

带记忆的超级GPT智能体,能做饭、煮咖啡、整理家务!

随着AI技术的快速迭代&#xff0c;Alexa、Siri、小度、天猫精灵等语音助手得到了广泛应用。但在自然语言理解和完成复杂任务方面仍然有限。 相比文本的标准格式&#xff0c;语音充满复杂性和多样性&#xff08;例如&#xff0c;地方话&#xff09;,传统方法很难适应不同用户的…

【每日OJ —— 20.有效的括号(栈)】

每日OJ —— 20.有效的括号&#xff08;栈&#xff09; 1.题目&#xff1a;20.有效的括号&#xff08;栈&#xff09;2.方法讲解2.1.解法2.1.1.算法讲解2.1.2.代码实现2.1.3.提交通过展示 1.题目&#xff1a;20.有效的括号&#xff08;栈&#xff09; 2.方法讲解 2.1.解法 利用…

2023 年 亚太赛 APMCM (B题)国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 问题一&#xff1a; 建立没有作物的玻璃温室内的温度和风速分…

C语言二十四弹--喝汽水问题

C语言解决喝汽水问题 题目&#xff1a;喝汽水&#xff0c;1瓶汽水1元&#xff0c;2个空瓶可以换一瓶汽水&#xff0c;给20元&#xff0c;可以喝多少汽水&#xff1f; 方法一、逐瓶购买法 思路&#xff1a;一瓶瓶的买 当空瓶有两个时&#xff0c;汽水数加1即可。 #include &…

MacOS 成为恶意软件活动的目标

Malwarebytes 警告称&#xff0c;一个针对 Mac 操作系统 (OS) 的数据窃取程序正在通过虚假的网络浏览器更新分发给毫无戒心的目标。 Atomic Stealer&#xff0c;也称为 AMOS&#xff0c;是 Mac OS 上流行的窃取程序。 Atomic Stealer (AMOS) 恶意软件最近被发现使用“ClearFa…