RT-Thread学习笔记(四)

RT-Thread学习笔记

  • 线程间同步
    • 信号量
    • 信号量的使用和管理
    • 动态创建信号量
    • 静态创建信号量
    • 获取信号量
    • 信号量同步实列
    • 互斥量
    • 互斥量的使用和管理
    • 互斥量动态创建
    • 互斥量静态创建
    • 互斥量获取和释放
    • 互斥量实例
    • 事件集
    • 事件集的使用和管理
    • 动态创建事件集
    • 静态初始化事件集
    • 发送和接收事件
    • 事件集实现线程同步实例

线程间同步

线程间同步的作用:多个执行单元(线程、中断)同时执行临界区,操作临界资源,会导致竟态产生,线程间同步确保多个线程在访问共享资源时,能够按照正确的顺序执行,避免数据冲突和逻辑错误。

临界区(Critical Section)是指一个程序中访问共享资源(如全局变量、内存、文件、硬件设备)的一段代码区域,在这段代码执行过程中,必须保证同一时刻最多只能有一个线程进入执行,以避免数据竞争和不一致问题。

✅ 具体作用如下:
防止数据竞争(竞态条件)
多个线程同时修改同一份数据时,如果没有同步机制,就可能出现数据错误或程序行为异常。
保证数据一致性
同步机制(如互斥锁、信号量等)可以确保同一时间只有一个线程访问临界资源,保证操作的原子性。
协调线程执行顺序
某些任务必须在另一个线程完成之后才能执行,比如:先读取文件,后处理数据。这种依赖关系就需要同步手段来控制执行顺序。
提高程序稳定性与可靠性
通过同步机制可以避免死锁、资源冲突等常见问题,使并发程序更健壮。

🔧RT-Thread OS提供了如下几种同步互斥机制:
互斥锁(mutex)
信号量(semaphore)
事件集(event)

信号量

信号量是一种轻型的用于解决线程间同步问题的内核对象,即信号量也是通过结构体定义描述的,线程可以获取释放它,从而达到同步互斥的目的。每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)

例如初始化信号量值为 1 ,当线程执行到临界区时要获取信号量,此时信号量值为 1,因此该线程可以获取到信号量,当信号量被获取一次后,信号量值会 减1,此时信号量值为0,当下一个线程需要获取信号量时,该线程就会被挂起,放到一个等待队列中去,等到信号量资源大于0时,回到对应的等待队列把挂起的线程唤醒,即将线程从挂起态切换为就绪态

信号量结构体
该结构体里有信号量的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 预留*/
};
typedef struct rt_semaphore *rt_sem_t;

信号量的使用和管理

对一个信号量的操作包含:创建 / 初始化信号量、获取信号量、释放信号量、删除 / 脱离信号量

首先要创建或初始化一个信号量,线程在临界区前获取信号量,在临界区后释放信号量

在这里插入图片描述

动态创建信号量

动态创建信号量函数

第一个参数是信号量的名字,第二个是信号量值,

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)#define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */

✅ 1. RT_IPC_FLAG_FIFO(先进先出)
含义:按线程进入等待队列的先后顺序来唤醒(排队公平)。
特点:谁先来谁先走,无视线程优先级。
应用场景:对响应时间要求不高,强调公平性,例如:简单同步、非实时任务。
🧠 示例: 线程 A、B、C 优先级不同,但如果是 A 先等待信号量,那释放信号量时 A 会先被唤醒,即使 B 优先级更高。

✅ 2. RT_IPC_FLAG_PRIO(优先级优先)
含义:按线程的优先级高低来决定唤醒顺序。
特点:优先级高的线程先被唤醒。
应用场景:对实时性要求高的系统,优先响应高优先级任务。
🧠 示例: 线程 A(低优先级)先等待资源,线程 B(高优先级)后加入等待队列;当资源释放时,线程 B 会被优先唤醒。

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

删除信号量函数

rt_err_t rt_sem_delete(rt_sem_t sem)

创建演示

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_sem_t sem1;int main(void)
{sem1 = rt_sem_create("sem1_demo", 1, RT_IPC_FLAG_FIFO);//先进先出if(sem1 == RT_NULL)//如果创建失败{LOG_E("rt_sem_create failed...\n");return -ENOMEM;}LOG_D("rt_sem_create successed...\n");//如果创建成功return RT_EOK;
}

信号量创建成功,但是信号量的名字不是sem1_demo,少了一个字符,因为信号量名字最多8个字符

在这里插入图片描述

改短一点就可以了

在这里插入图片描述

静态创建信号量

初始化信号量

第一个参数是rt_sem_t 类型的结构体指针,需要定义,第二个参数是信号量的名字,第三个参数是信号量值,第四个参数是flag,与动态创建一样

rt_err_t rt_sem_init(rt_sem_t    sem,const char *name,rt_uint32_t value,rt_uint8_t  flag)

信号量脱离

脱离信号量就是让信号量对象从内核对象管理器中脱离,适用于静态初始化的信号量使用该函数后,内核先唤醒所有挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器中脱离。原来挂起在信号量上的等待线程将获得 - RT_ERROR 的返回值。

rt_err_t rt_sem_detach(rt_sem_t sem)

初始化演示

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_sem_t sem1;struct rt_semaphore sem2;//定义一个信号量结构体用于初始化信号量int main(void)
{int ret = 0;//用于接收初始化的返回值sem1 = rt_sem_create("sem1", 1, RT_IPC_FLAG_FIFO);//先进先出if(sem1 == RT_NULL)//如果创建失败{LOG_E("rt_sem_create failed...\n");return -ENOMEM;}LOG_D("rt_sem_create successed...\n");//如果创建成功ret = rt_sem_init(&sem2, "sem2", 5, RT_IPC_FLAG_FIFO);if(ret < 0)//如果初始化失败{LOG_E("rt_sem_init failed...\n");return ret;}LOG_D("rt_sem_init successed...\n");//如果初始化成功return RT_EOK;
}

在这里插入图片描述

获取信号量

假设有一个临界资源是让 i++,如果两个线程都可以操作该临界资源,即两个线程都会让 i++,这时候就会发生竞争,因此就需要使用信号量的获取和释放

获取信号量函数

第一个参数是要获取哪个信号量,第二个参数timeout决定了是否阻塞等待,RT_WAITING_FOREVER表示一直等待,直到有信号量,T_WAITING_NO表示不等待,直接返回

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout)#define RT_WAITING_FOREVER              -1              /**< Block forever until get resource. */
#define RT_WAITING_NO                   0               /**< Non-block. */

另一个获取信号量的函数,等价于rt_sem_take(rt_sem_t sem, T_WAITING_NO),即不等待,直接返回

rt_err_t rt_sem_trytake(rt_sem_t sem)

释放信号量函数

在操作完临界资源后要去释放信号量,不然其他线程无法获取这个信号量

rt_err_t rt_sem_trytake(rt_sem_t sem)

信号量同步实列

信号量的同步是指通过信号量(semaphore)来协调多个线程或任务之间的执行时机,使它们按照正确的顺序运行,从而实现“谁先做、谁后做”的同步控制关系。

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_sem_t sem1;rt_thread_t th1,th2;int flag = 0;struct rt_semaphore sem2;//定义一个信号量结构体用于初始化信号量void th1_entry(void *parameter)//线程1入口函数
{while(1)//循环执行{rt_thread_mdelay(5000);//一开始处于阻塞状态,会跳转到线程2rt_sem_take(sem1, RT_WAITING_FOREVER );flag ++;if(flag == 100)flag = 0;rt_kprintf("th1_entry[%d]\n",flag);rt_sem_release(&sem2);}
}void th2_entry(void *parameter)//线程2入口函数
{while(1)//一开始跳转到线程2不会立即执行线程2,因为信号量2的值为0,因此处于挂起状态{rt_sem_take(&sem2, RT_WAITING_FOREVER);if(flag > 0)flag --;rt_kprintf("th2_entry[%d]\n",flag);rt_sem_release(sem1);rt_thread_mdelay(1000);}
}int main(void)
{int ret = 0;//用于接收初始化的返回值sem1 = rt_sem_create("sem1", 1, RT_IPC_FLAG_FIFO);//初始化信号量1的值为1if(sem1 == RT_NULL)//如果创建失败{LOG_E("rt_sem_create failed...\n");return -ENOMEM;}LOG_D("rt_sem_create successed...\n");//如果创建成功ret = rt_sem_init(&sem2, "sem2", 0, RT_IPC_FLAG_FIFO);//初始化信号量2的值为0if(ret < 0)//如果初始化失败{LOG_E("rt_sem_init failed...\n");return ret;}LOG_D("rt_sem_init successed...\n");//如果初始化成功th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);return RT_EOK;
}

分别创建两个线程,flag作为临界区,两个线程都会对flag进行操作,因此需要通过信号量防止竞态的发生,在初始化时,信号量1的值为1,信号量2的值为2,线程th1获取的是信号量1,释放的是信号量2,线程2获取的是信号量2,释放的是信号量1

在一开始,线程1的入口函数先用一个延时阻塞,这时线程1被挂起,线程2被调度,但是此时的信号量2值为0,线程2获取不到信号量,因此线程2被挂起,等到线程1的延时结束,又回到线程1,此时线程1获取信号量1成功,执行相应的操作,然后释放信号量2,信号量1的值从1变为0,信号量2的值从0变为1,线程1执行完,又轮到线程2执行,此时信号量2值为1,因此线程2能够获取信号量2,对应的操作能够执行,线程2又释放信号量1,此时信号量1的值又变为1,信号量2的值又变为0,依次循环,就实现了信号的同步操作

请添加图片描述

互斥量

互斥量体现的是排他性,也是解决多线程同时操作临界区临界资源导致的竟态的一种方法。(类似于特殊的信号量——二值信号量(只有 01 )),即一个线程在执行时,另一个线程不能执行,不强调顺序

与信号量的区别:信号量可由不同线程释放,互斥量只能由同一线程进行释放。

互斥量的使用和管理

互斥量的操作包含:创建 / 初始化互斥量、获取互斥量、释放互斥量、删除 / 脱离互斥量

在这里插入图片描述

互斥量动态创建

不再使用互斥量时,通过删除互斥量以释放系统资源,适用于动态创建的互斥量当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是 - RT_ERROR

互斥量动态创建函数

rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)#define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */

互斥量删除函数

rt_err_t rt_mutex_delete(rt_mutex_t mutex)
#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;int main(void)
{mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");return RT_EOK;
}

在这里插入图片描述

互斥量静态创建

使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是 -RT_ERROR ) ,然后系统将该互斥量从内核对象管理器中脱离。

互斥量静态创建函数

第一个参数也是要先定义一个结构体作为参数传入

rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)

互斥量脱离函数

rt_err_t rt_mutex_detach(rt_mutex_t mutex)
#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;struct rt_mutex mutex2;//定义一个结构体用于静态创建互斥量传参int main(void)
{int ret;//用于接收静态创建的返回值//动态创建mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");//静态创建ret = rt_mutex_init(&mutex2, "mutex2", RT_IPC_FLAG_FIFO);if(ret < 0){LOG_E("mutex2 rt_mutex_init failed...\n");return ret;}LOG_D("mutex2 rt_mutex_init successed...\n");return RT_EOK;
}

在这里插入图片描述

互斥量获取和释放

互斥量获取函数

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout)#define RT_WAITING_FOREVER              -1              /**< Block forever until get resource. */
#define RT_WAITING_NO                   0               /**< Non-block. */

另一种获取函数,等价于rt_mutex_take(rt_mutex_t mutex,RT_WAITING_NO)

rt_err_t rt_mutex_trytake(rt_mutex_t mutex)

互斥量释放函数

rt_err_t rt_mutex_release(rt_mutex_t mutex)

获取相当于上锁,释放相当于解锁,上了锁的要等到解锁后才能使用,例如上厕所,有一个人进去了,上了锁,下一个人要使用这个厕所的话一定要等到上一个人解锁出来了,下一个人才能进入厕所,上锁,且没有顺序,谁先来就谁去

上面的内容都是创建或初始化,没有调用过删除或这脱离,在这里调用一下,看看效果是怎么样的,但是一般情况下一个线程或者信号量或互斥量创建之后,就会一直使用,因此不需要去删除,但有时也会存在特殊情况

在这里插入图片描述

在这里插入图片描述

互斥量实例

没有互斥量

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;rt_thread_t th1,th2;int flag1 ,flag2;void th1_entry(void *parameter)//线程1入口函数
{while(1)//循环执行{flag1 ++;rt_thread_mdelay(1000);}
}void th2_entry(void *parameter)//线程2入口函数
{while(1){flag1 ++;flag2 ++;    rt_kprintf("flag1:[%d] flag2:[%d]\n",flag1,flag2);rt_thread_mdelay(1000);}
}int main(void)
{//动态创建mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);return RT_EOK;
}

上面的代码定义了两个变量,flag1和flag2作为临界区,代码原本的功能是让flag1和flag2同步增加,应该两个值是相等的,创建两个线程同时操作这两个变量,在没有互斥量的情况下,会出现不同步和覆盖的可能,导致两个变量不一致、不连续,如下图结果,flag1 != flag2,因此要加上互斥量进行保护

请添加图片描述

加上互斥量

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;rt_thread_t th1,th2;int flag1 ,flag2;void th1_entry(void *parameter)//线程1入口函数
{while(1)//循环执行{rt_mutex_take(mutex1, RT_WAITING_FOREVER);//上锁flag1 ++;rt_thread_mdelay(1000);flag2 ++;rt_mutex_release(mutex1);//解锁}
}void th2_entry(void *parameter)//线程2入口函数
{while(1){rt_mutex_take(mutex1, RT_WAITING_FOREVER);//上锁flag1 ++;flag2 ++;rt_mutex_release(mutex1);//解锁rt_kprintf("flag1:[%d] flag2:[%d]\n",flag1,flag2);rt_thread_mdelay(1000);}
}int main(void)
{//动态创建mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);return RT_EOK;
}

加上了互斥量对临界区的保护,同一时间,只有一个线程能够对临界区进行操作,操作完成后解锁了,下一个线程才能对临界区进行操作,这样两个变量就能够同步相等了

请添加图片描述

事件集

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

一个线程和多个事件的关系可设置为: 其中任意一个事件唤醒 线程,或几个事件都到达后唤醒线程,多个事件集合可以用一个32bit无符号整型变量来表示,变量的每一位代表一个事件,线程通过"逻辑与"或"逻辑或"将一个或多个事件关联起来,形成事件组合

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

  1. 事件只与线程相关,事件间相互独立
  2. 事件仅用于同步,不提供数据传输功能
  3. 事件无排队性,即多次向线程发送同一事件(如果线程还未来得及读走),其效果等同于只发送一次

在这里插入图片描述

事件集的使用和管理

对一个事件集的操作包含:创建/初始化事件集、发送事件、接收事件、删除/脱离事件集

在这里插入图片描述

动态创建事件集

动态创建事件集函数

函数的返回值是一个rt_event_t数据类型的结构体,其中结构体中有一个rt_uint32_t数据类型的 set 集合,这个集合里面每一位代表一个事件,因此一个事件集里最多支持32个事件

rt_event_t rt_event_create(const char *name, rt_uint8_t flag)#define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */struct rt_event
{struct rt_ipc_object parent;                        /**< inherit from ipc_object */rt_uint32_t          set;                           /**< event set */
};
typedef struct rt_event *rt_event_t;

删除事件集函数

rt_err_t rt_event_delete(rt_event_t event)

静态初始化事件集

静态初始化事件集函数

要创建一个rt_event_t类型的结构体指针作为参数传入

rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)

事件集脱离函数

rt_err_t rt_event_detach(rt_event_t event)
#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_event_t event1;struct rt_event event2;int main(void)
{int ret;//动态创建事件集event1 = rt_event_create("event1", RT_IPC_FLAG_FIFO);if(event1 == RT_NULL)//如果创建失败{LOG_E("event1 rt_event_create failed...\n");return -ENOMEM;}LOG_D("event1 rt_event_create successed...\n");//如果创建成功//静态初始化事件集ret = rt_event_init(&event2, "event2", RT_IPC_FLAG_FIFO);if(ret < 0)//如果初始化失败{LOG_E("event2 rt_event_init failed...\n");return ret;}LOG_D("event2 rt_event_init successed...\n");//如果初始化成功return RT_EOK;
}

在这里插入图片描述

发送和接收事件

发送事件函数

第一个参数是事件集,第二个参数是事件集中的哪一个事件

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);

接收事件函数

第一个参数是事件集,第二个参数是事件集中的哪一个事件,第三个参数是与还是或还是清除了,即接收完这个位后要不要进行清除,第四个参数是超时时间,第五个参数用于存放接收到的值

rt_err_t rt_event_recv(rt_event_t   event,rt_uint32_t  set,rt_uint8_t   opt,rt_int32_t   timeout,rt_uint32_t *recved);
//opt参数                       
#define RT_EVENT_FLAG_AND               0x01            /**< logic and */
#define RT_EVENT_FLAG_OR                0x02            /**< logic or */
#define RT_EVENT_FLAG_CLEAR             0x04            /**< clear flag *///timeout参数
#define RT_WAITING_FOREVER              -1              /**< Block forever until get resource. */
#define RT_WAITING_NO                   0               /**< Non-block. */

事件集实现线程同步实例

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_event_t event1;rt_thread_t th1,th2,th3;//分别用一个位来表示事件集中的一个事件
#define EVENT_FLAG_1 (0x1 << 0)//事件1
#define EVENT_FLAG_2 (0x1 << 1)//事件2
#define EVENT_FLAG_3 (0x1 << 2)//事件3void th1_entry(void *parameter)//线程1入口函数
{while(1)//循环执行{rt_event_recv(event1, EVENT_FLAG_1, RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND, RT_WAITING_FOREVER, NULL);//接收事件1rt_kprintf("th1_entry...\n");rt_event_send(event1, EVENT_FLAG_2);//发送事件2rt_thread_mdelay(1000);}
}void th2_entry(void *parameter)//线程2入口函数
{while(1){rt_event_recv(event1, EVENT_FLAG_2, RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND, RT_WAITING_FOREVER, NULL);//接收事件2rt_kprintf("th2_entry...\n");rt_event_send(event1, EVENT_FLAG_3);//发送事件3rt_thread_mdelay(1000);}
}void th3_entry(void *parameter)//线程3入口函数
{while(1){rt_event_recv(event1, EVENT_FLAG_3, RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND, RT_WAITING_FOREVER, NULL);//接收事件3rt_kprintf("th3_entry...\n");rt_event_send(event1, EVENT_FLAG_1);//发送事件1rt_thread_mdelay(1000);}
}int main(void)
{//动态创建事件集event1 = rt_event_create("event1", RT_IPC_FLAG_FIFO);if(event1 == RT_NULL){LOG_E("event1 rt_event_create failed...\n");return -ENOMEM;}LOG_D("event1 rt_event_create successed...\n");th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");th3 = rt_thread_create("th3", th3_entry, NULL, 512, 20, 5);if(th3 == RT_NULL){LOG_E("th3 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th3 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);rt_thread_startup(th3);rt_event_send(event1, EVENT_FLAG_1);//先发送一次事件1return RT_EOK;
}

在这里插入图片描述

可以看到确实是按照线程 1、2、3 顺序执行的

请添加图片描述

创建一个临界区,观察是否能起到保护临界区的作用

在这里插入图片描述

可以看到无论每个线程的延时函数放在哪里,每个线程都是按顺序执行的,对临界区起到了保护,避免了竞态现象的发生

请添加图片描述

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

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

相关文章

element ui el-col的高度不一致导致换行

问题&#xff1a;ell-col的高度不一致导致换行&#xff0c;刷新后审查el-col的高度一致 我这边是el-col写的span超过了24&#xff0c;自行换行&#xff0c;测试发现初次进入里面的高度渲染的不一致&#xff0c;有的是51px有的是51.5px 问题原因分析 Flex布局换行机制 Elemen…

现代化Android开发:Compose提示信息的最佳封装方案

在 Android 开发中&#xff0c;良好的用户反馈机制至关重要。Jetpack Compose 提供了现代化的 UI 构建方式&#xff0c;但提示信息(Toast/Snackbar)的管理往往显得分散。本文将介绍如何优雅地封装提示信息&#xff0c;提升代码可维护性。 一、基础封装方案 1. 简单 Snackbar …

【C++语法】类和对象(2)

4.类和对象&#xff08;2&#xff09; 文章目录 4.类和对象&#xff08;2&#xff09;类的六个默认成员函数(1)构造函数&#xff1a;构造函数特点含有缺省参数的构造函数构造函数特点&#xff08;续&#xff09;注意事项构造函数补充 前面总结了有关对象概念&#xff0c;对比 C…

【自然语言处理与大模型】vLLM部署本地大模型②

举例上一篇文章已经过去了几个月&#xff0c;大模型领域风云变幻&#xff0c;之前的vLLM安装稍有过时&#xff0c;这里补充一个快速安装教程&#xff1a; # 第一步&#xff1a;创建虚拟环境并激活进入 conda create -n vllm-0.8.4 python3.10 -y conda activate vllm-0…

26 Arcgis软件常用工具有哪些

一、画图改图工具&#xff08;矢量编辑&#xff09;‌ ‌挪位置工具&#xff08;移动工具&#xff09;‌ 干哈的&#xff1f;‌选中要素‌&#xff08;比如地块、道路&#xff09;直接拖到新位置&#xff0c;或者用坐标‌X/Y偏移‌批量移动&#xff0c;适合“整体搬家”。 ‌磁…

QNX/LINUX/Android系统动态配置动态库.so文件日志打印级别的方法

背景 通常我们会在量产的产品上&#xff0c;配置软件仅打印少量日志&#xff0c;以提升产品的运行性能。同时我们要考虑预留方法让软件能够拥有能力可以在烧录版本后能够通过修改默写配置&#xff0c;打印更多日志。因为量产后的软件通常开启熔断与加密&#xff0c;不能够轻松…

WebGL图形编程实战【4】:光影交织 × 逐片元光照与渲染技巧

现实世界中的物体被光线照射时&#xff0c;会反射一部分光。只有当反射光线进人你的眼睛时&#xff0c;你才能够看到物体并辩认出它的颜色。 光源类型 平行光&#xff08;Directional Light&#xff09;&#xff1a;光线是相互平行的&#xff0c;平行光具有方向。平行光可以看…

【Hive入门】Hive基础操作与SQL语法:DDL操作全面指南

目录 1 Hive DDL操作概述 2 数据库操作全流程 2.1 创建数据库 2.2 查看数据库 2.3 使用数据库 2.4 修改数据库 2.5 删除数据库 3 表操作全流程 3.1 创建表 3.2 查看表信息 3.3 修改表 3.4 删除表 4 分区与分桶操作 4.1 分区操作流程 4.2 分桶操作 5 最佳实践与…

YOLO数据处理

YOLO&#xff08;You Only Look Once&#xff09;的数据处理流程是为了解决目标检测领域的核心挑战&#xff0c;核心目标是为模型训练和推理提供高效、规范化的数据输入。其设计方法系统性地解决了以下关键问题&#xff0c;并对应发展了成熟的技术方案&#xff1a; 一、解决的问…

Ubuntu-Linux中vi / vim编辑文件,保存并退出

1.打开文件 vi / vim 文件名&#xff08;例&#xff1a; vim word.txt &#xff09;。 若权限不够&#xff0c;则在前方添加 sudo &#xff08;例&#xff1a;sudo vim word.txt &#xff09;来增加权限&#xff1b; 2.进入文件&#xff0c;按 i 键进入编辑模式。 3.编辑结…

PCL绘制点云+法线

读取的点云ASCII码文件&#xff0c;每行6个数据&#xff0c;3维坐标3维法向 #include <iostream> #include <fstream> #include <vector> #include <string> #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pc…

如何在学习通快速输入答案(网页版),其他学习平台通用,手机上快速粘贴

目录 1、网页版&#xff08;全平台通用&#xff09; 2、手机版&#xff08;学习通&#xff0c;其他平台有可能使用&#xff09; 1、网页版&#xff08;全平台通用&#xff09; 1、首先CtrlC复制好答案 2、在学习通的作业里输入1 3、对准1&#xff0c;然后鼠标右键 &#xff…

002 六自由度舵机机械臂——姿态解算理论

00 DH模型的核心概念 【全程干货【六轴机械臂正逆解计算及仿真示例】】 如何实现机械臂的逆解计算-机器谱-robotway DH模型是机器人运动学建模的基础方法&#xff0c;通过​​四个参数​​描述相邻关节坐标系之间的变换关系。其核心思想是将复杂的空间位姿转换分解为绕轴旋转…

pymongo功能整理与基础操作类

以下是 Python 与 PyMongo 的完整功能整理&#xff0c;涵盖基础操作、高级功能、性能优化及常见应用场景&#xff1a; 1. 安装与连接 (1) 安装 PyMongo pip install pymongo(2) 连接 MongoDB from pymongo import MongoClient# 基础连接&#xff08;默认本地&#xff0c;端口…

Trae+DeepSeek学习Python开发MVC框架程序笔记(四):使用sqlite存储查询并验证用户名和密码

继续通过Trae向DeepSeek发问并修改程序&#xff0c;实现程序运行时生成数据库&#xff0c;用户在系统登录页面输入用户名和密码后&#xff0c;控制器通过模型查询用户数据库表来验证用户名和密码&#xff0c;验证通过后显示登录成功页面&#xff0c;验证失败则显示登录失败页面…

如何识别金融欺诈行为并进行分析预警

金融行业以其高效便捷的服务深刻改变了人们的生活方式。然而,伴随技术进步而来的,是金融欺诈行为的日益猖獗。从信用卡盗刷到复杂的庞氏骗局,再到网络钓鱼和洗钱活动,金融欺诈的形式层出不穷,其规模和影响也在不断扩大。根据全球反欺诈组织(ACFE)的最新报告,仅2022年,…

纷析云:开源财务管理软件的创新与价值

在企业数字化转型中&#xff0c;纷析云作为一款优秀的开源财务管理软件&#xff0c;正为企业财务管理带来新变革&#xff0c;以下是其核心要点。 一、产品概述与技术架构 纷析云采用微服务架构&#xff0c;功能组件高内聚低耦合&#xff0c;可灵活扩展和定制。前端基于现代框…

蛋白质大语言模型ESM介绍

ESM(Evolutionary Scale Modeling)是 Meta AI Research 团队开发的一系列用于蛋白质的预训练语言模型。这些模型在蛋白质结构预测、功能预测和蛋白质设计等领域展现出了强大的能力。以下是对 ESM 的详细介绍: 核心特点 大规模预训练:基于大规模蛋白质序列数据进行无监督学…

OpenCv高阶(七)——图像拼接

目录 一、图像拼接的原理过程 1. 特征检测与描述&#xff08;Feature Detection & Description&#xff09; 2. 特征匹配&#xff08;Feature Matching&#xff09; 3. 图像配准&#xff08;Image Registration&#xff09; 4. 图像变换与投影&#xff08;Warping&…

Native层Trace监控性能

一、基础实现方法 1.1 头文件引用 #include <utils/Trace.h> // 基础版本 #include <cutils/trace.h> // 兼容旧版本1.2 核心宏定义 // 区间追踪&#xff08;推荐&#xff09; ATRACE_BEGIN("TraceTag"); ...被监控代码... ATRACE_END();// 函数级自…