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,一经查实,立即删除!

相关文章

DockerHub 无法访问 - 解决办法

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

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

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

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

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

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…

带记忆的超级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; 建立没有作物的玻璃温室内的温度和风速分…

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

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

ImportError: cannot import name ‘contextfilter‘ from ‘jinja2‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

汇编-pop出栈指令

32位汇编 执行动作分为两步&#xff1a; 第一步&#xff1a;读出数据 第二步&#xff1a;改变栈地址 如果操作数是16位&#xff0c; 则ESP加2&#xff1b; 如果操作数是32位&#xff0c; 则ESP加4 espesp2 或 espesp4 格式&#xff1a;

IDEA中 java: 警告: 源发行版 11 需要目标发行版 11 如何解决

步骤1找到项目结构&#xff0c;下面有两种方式 步骤2找到 模块中对应的项目&#xff0c;修改对应的源的语言级别和依赖的模块SDK(M) 步骤3&#xff0c;启动一下&#xff0c;看有无问题&#xff0c; 步骤4&#xff0c;去文件-->设置-->构建、执行、部署-->编译器-->…

中职组网络安全B模块-渗透提权2

任务五&#xff1a;渗透提权2 任务环境说明&#xff1a; 仅能获取xxx的IP地址 用户名&#xff1a;test&#xff0c;密码&#xff1a;123456 访问服务器主机&#xff0c;找到主机中管理员名称&#xff0c;将管理员名称作为Flag值提交&#xff1b; Flag:doyoudoyoudo 访问服…

感恩三十载 再创新辉煌——中国音乐著作权协会成立30周年暨著作权集体管理制度实施30周年纪念大会在京召开

感恩三十载 再创新辉煌 2023年11月19日&#xff0c;中国音乐著作权协会成立30周年暨著作权集体管理制度实施30周年纪念大会在北京举行。中宣部副部长张建春&#xff0c;国际作者和作曲者协会联合会&#xff08;CISAC&#xff09;总干事甘迪奥龙&#xff08;Gadi Oron&#xff0…

深入了解批处理文件:从基础到实例

1. 什么是批处理文件&#xff1f; 批处理文件是一种包含一系列命令的文本文件&#xff0c;通常用于自动化执行一系列任务。在不同操作系统中&#xff0c;批处理也有不同的名称&#xff0c;如在Windows中被称为批处理文件&#xff08;.bat&#xff09;&#xff0c;而在Linux中则…

1、基础入门——操作系统文件下载反弹SHELL防火墙绕过

名词解释 POC&#xff1a;验证漏洞存在的代码&#xff1b; EXP&#xff1a;利用漏洞的代码&#xff1b; payload&#xff1a;漏洞利用载荷&#xff0c; shellcode&#xff1a;漏洞代码&#xff0c; webshell&#xff1a;特指网站后门&#xff1b; 木马&#xff1a;强调控制…

Day40:139.单词拆分、背包问题总结

文章目录 139.单词拆分思路代码实现 背包问题总结背包类型递推公式 139.单词拆分 题目链接 思路 确定dp数组以及下标的含义 dp[i] : 从0开始长度为i的字符串是否可以拆分为一个或多个在字典中出现的单词确定递推公式 如果确定dp[j] 是true&#xff0c;且 [j, i] 这个区间的子…

关于ElectronVue3中集成讯飞星火AI

前言&#xff1a;我的最终目的是为了在QQ上集成一个AI机器人&#xff0c;因此在这里先实现一个简单的集成 先上效果图 总体还是很简单的&#xff0c;我在调用websock获取回复内容的基础上另外集成了一个事件总线&#xff0c;让我们在调用获取消息的时候能够更加方便快捷 工具代…

联想拯救者Lenovo Legion R9000K 2021H(82N6)原装出厂Windows10/Win11系统ISO镜像

链接&#xff1a;https://pan.baidu.com/s/13NkeCXNdV0Ib5eeRnZUeAQ?pwdnlr7 提取码&#xff1a;nlr7 拯救者笔记本电脑原厂WIN系统自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文…

启发式搜索算法-人工智能

第1关:评估函数和启发信息 第2关:A*搜索算法 class Array2D:"""说明:1.构造方法需要两个参数,即二维数组的 宽和高2.成员变量w和h是二维数组的宽和高3.使用:‘对象[x][y]’可以直接取到相应的值4.数组的默认值都是0"""def __init__(s…

使用PySpark 结合Apache SystemDS 进行信号处理分析 (离散傅立叶变换)的简单例子

文章大纲 简介 :什么是 SystemDS ?环境搭建与数据 准备数据预处理模型训练 与 结果评估参考文献简介 :什么是 SystemDS ? SystemDS is an open source ML system for the end-to-end data science lifecycle from data integration, cleaning, and feature engineering, ov…