在多线程系统中,多个线程之间需要“协调”,就像你和同事们⼀起合作⼯作。RT-Thread 提供了
信号量、互斥量和事件集来帮助线程间进⾏同步。
==信号量(Semaphore==):就像⻋站发⻋信号,只有收到信号的线程才能开始执⾏任务。
==互斥量(Mutex==):确保多个线程不会同时访问同⼀资源,避免混乱。⽐如,打印机只能同时被
⼀个⼈使⽤,
==互斥量== :相当于⼀把钥匙,谁拿到钥匙谁就可以⽤打印机。
==事件集==:允许线程在多个事件触发时进⾏操作。就像你等着多个快递到达,可以选择全部到⻬
再处理,或者⼀个到达就处理⼀个。
==**线程间通信** ==
线程之间需要交换信息,RT-Thread 提供了邮箱和消息队列来实现。邮箱就像是⼀个固定容量的
收件箱,⽽消息队列是⼀个可伸缩的队列,消息可以有不同的⻓度。邮箱更⾼效,但消息队列更
加灵活。
===信号量(Semaphore) ==
信号量可以理解为电影院的座位数(资源数量)。当所有座位都满了,新的观众(线程)就需要等
前⾯的观众离开(释放资源)才能进⼊。当有空位时,观众可以进⼊。信号量通过控制资源的数
量来管理多个线程对资源的访问。
==信号量的⼯作机制 ==
信号量的核⼼思想是控制资源的访问数量。信号量的值代表了可以访问的资源数量:
获取信号量:当资源可⽤时,线程可以获取信号量,信号量的值减1。如果信号量的值为0,线
程就会等待,直到资源释放。
释放信号量:使⽤完资源后,线程释放信号量,信号量的值加1,允许其他线程访问资源
==rt_sem_t ==是 RT-Thread 操作系统中用于表示信号量的类型。信号量是一种轻型的用于解决线程间同步问题的内核对象
线程可以获取或释放它,从而达到同步或互斥的目的。通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。
其值的含义分两种情况:
0:表示没有积累下来的 release 释放信号量操作,且有可能有在此信号量上阻塞的线程。
正值:表示有一个或多个 release 释放信号量操作。
==rt_sem_take==(获取信号量api)
⽤于获取信号量(P 操作),线程会尝试获取信号量,如果信号量的计数值为 0,线程会进⼊等待状态,直到信号量被释放或超时。
rt_err_t
rt_sem_take(rt_sem_t sem, rt_int32_t time);
参数说明:
sem
:信号量的句柄。
time
:超时时间(单位为系统 tick),表⽰最⼤等待时间。如果设置为
_FOREVER
,线程会⼀直等待。
返回值:
成功:返回
RT_EOK
。 失败:返回 -
RT_ETIMEOUT
rt_sem_release
⽤于释放信号量(V 操作),增加信号量的计数值,并唤醒等待该信号量的线程。
rt_err_t rt_sem_release(rt_sem_t sem); 参数说明:
sem :信号量的句柄。 返回值:
成功:返回 RT_EOK 。
失败:返回相应的错误代码
(超时)或其他错误代码
==`rt_sem_control`==
是 RT-Thread 操作系统中的一个函数,用于控制信号量的行为。通过这个函数,你可以获取或设置信号量的当前值,重置信号量等。
以下是 `rt_sem_control` 函数的原型:
`c复制代码 rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg);`
### 参数说明
1. **sem**: 要控制的 `rt_sem_t` 类型的信号量句柄。
2. **cmd**: 控制命令,可以是以下几种之一:
- `RT_IPC_CMD_RESET`: 重置信号量,将其值设置为初始值(通常为0)。
- `RT_IPC_CMD_GET_VALUE`: 获取当前信号量的值。
- `RT_IPC_CMD_SET_VALUE`: 设置信号量的值。
3. **arg**: 指向一个变量的指针,根据不同的命令,该变量用于存储或设置信号量的值。
### 返回值
- 成功时返回 `RT_EOK`。
- 失败时返回相应的错误码。
在 RT-Thread 中,信号量(semaphore)是⼀种常⻅的同步机制,主要⽤于任务之间的
同步与互斥。它可以通过信号量的计数机制来协调多个线程的运⾏,确保资源的安全访
问。信号量的开发场景通常有以下⼏种:
==1. 任务同步
多个任务之间需要在某些时间点进⾏同步时,可以使⽤信号量。信号量可以⽤来实现线程
间的等待和通知机制。例如,当⼀个任务完成某项⼯作后,发送⼀个信号量,通知等待该
信号量的任务继续执⾏。
应⽤场景⽰例:
⼀个任务处理传感器数据,处理完成后通知另⼀个任务进⾏数据发送。
⼀个任务等待外部事件(如外部中断)发⽣,中断发⽣后释放信号量,任务获取信号
量后继续执⾏。
===2. 任务互斥
当多个任务需要访问共享资源时,可以使⽤信号量来实现互斥访问。互斥信号量
(Mutex)可以保证同⼀时刻只有⼀个任务能够访问共享资源,避免出现竞争条件和数据
不⼀致的情况。
应⽤场景⽰例:
多个任务同时访问同⼀个硬件外设(如串⼝、I2C 总线)时,通过信号量保证同⼀时刻
只有⼀个任务访问外设,防⽌访问冲突。
多个任务需要访问同⼀块内存数据时,使⽤信号量确保数据访问的独占性。
==3. 事件触发
某些外部事件发⽣后,需要通知任务执⾏相应的处理逻辑。这种场景下,外部中断或事件
处理函数可以通过释放信号量来通知任务进⾏处理。任务会等待信号量,当信号量被释放
时,任务被唤醒并开始执⾏。
应⽤场景⽰例:
⼀个任务等待按键事件,当按键被按下时,中断处理程序释放信号量,任务获取信号
量并执⾏按键响应逻辑。
⽹络模块等待数据接收事件,当⽹络数据到达时,触发信号量,通知任务进⾏数据处
理。
==4. 限量资源管理
信号量可以⽤来管理有限的资源,信号量的计数值可以表⽰可⽤资源的数量。当⼀个任务
获取到资源时,信号量的计数值减少;当任务释放资源时,信号量的计数值增加。如果信
号量的计数值为 0,则任务必须等待资源可⽤。
应⽤场景⽰例:
⼀个系统有多个任务需要访问⼀个有限的资源池(如线程池、内存池),信号量可以表
⽰可⽤资源的数量,确保任务只能在资源可⽤时进⾏访问。
限制并发访问的数量,⽐如同⼀时刻只能有固定数量的任务访问某个服务
实验1:
![[Pasted image 20250120155814.png]]
==实验结果分析:==
当代码运行时由于两个线程的优先级相等 所以会同时进入但是线程1由于delay(100)才获取信号量所以会挂起 如图所示线程2进入获取一个座位;
然后delay20s后释放信号量 线程1继续运行;
==代码:==
```
rt_sem_t sem = RT_NULL;
void thread_entry1(void *parameter)
{
rt_thread_delay(100);
rt_kprintf("线程1: 等待进入电影院\n");
rt_sem_take(sem, RT_WAITING_FOREVER);
rt_kprintf("线程1: 进入电影院!\n");
}
// 尝试获取信号量
// 线程2:离开电影院,释放信号量
void thread_entry2(void *parameter)
{
rt_sem_take(sem, RT_WAITING_FOREVER);
rt_kprintf("线程2: 进入电影院,获取一个座位\n");
rt_thread_delay(20000);
rt_kprintf("线程2: 离开电影院,释放一个座位\n");
rt_sem_release(sem);
// 释放信号量
}
int main(void)
{
// 初始化信号量,初始值为1,表示电影院有1个座位
sem = rt_sem_create("sem", 1, RT_IPC_FLAG_PRIO);
// 创//建两个线程
rt_thread_t tid1 = rt_thread_create("t1", thread_entry1, RT_NULL, 1024, 25, 10);
// 解释:
rt_thread_t tid2 = rt_thread_create("t2", thread_entry2, RT_NULL, 1024, 25, 10);
// 启动线程
rt_thread_startup(tid1);
rt_thread_startup(tid2);
return 0;
}
```
实验2:
```
rt_sem_t sem = RT_NULL;
void thread_entry1(void *parameter)
{
rt_kprintf("线程1: 等待进入电影院,但是没有座位\n");
rt_sem_take(sem, RT_WAITING_FOREVER); // 等待信号量
rt_kprintf("线程1: 进入电影院有座位!\n");
}
void thread_entry2(void *parameter)
{
rt_kprintf("线程2: 进入电影院,打扫卫生\n");
rt_thread_delay(10000);
rt_sem_release(sem); // 释放信号量
rt_kprintf("线程2: 打扫完毕有座位\n");
}
int main(void)
{
// 初始化信号量,初始值为0,表示电影院有0个座位
sem = rt_sem_create("sem", 0, RT_IPC_FLAG_PRIO);
// 创建两个线程并释放
rt_thread_t tid1 = rt_thread_create("t1", thread_entry1, RT_NULL, 1024, 25, 10);
rt_thread_t tid2 = rt_thread_create("t2", thread_entry2, RT_NULL, 1024, 25, 10);
if (tid1 != RT_NULL)
{
rt_thread_startup(tid1);
rt_kprintf("线程1启动成功! \n");
}
else
{
rt_kprintf("线程1启动失败! \n");
}
if (tid2 != RT_NULL)
{
rt_thread_startup(tid2);
rt_kprintf("线程2启动成功! \n");
}
else
{
rt_kprintf("线程2启动失败! \n");
}
}
```
![[Pasted image 20250120165057.png]]
同步信号量 必须等线程二执行完毕才能执行线程一
===2. 任务互斥 实验3
```
rt_sem_t sem = RT_NULL;
rt_int16_t count;
rt_int16_t max_count = 5;
void thread_entry1(void *parameter)
{
while (1)
{
rt_sem_take(sem, RT_WAITING_FOREVER); // 等待信号量
rt_kprintf("线程1: 进入电影院,有%d个座位\n", count);
count++;
rt_kprintf("线程1: 打扫电影院,增加%d个座位\n", count);
rt_thread_delay(100);
rt_sem_release(sem); // 释放信号量
rt_thread_delay(500); // 让出cpu给其他线程
}
}
void thread_entry2(void *parameter)
{
while (1)
{
rt_sem_take(sem, RT_WAITING_FOREVER); // 等待信号量
rt_kprintf("线程2: 进入电影院,有%d个座位\n", count);
count++;
rt_kprintf("线程2: 打扫电影院,增加%d个座位\n", count);
rt_thread_delay(100);
rt_sem_release(sem); // 释放信号量
rt_thread_delay(500); // 让出cpu给其他线程
}
}
int main(void)
{
// 初始化信号量,初始值为0,表示电影院有0个座位
sem = rt_sem_create("sem", 1, RT_IPC_FLAG_PRIO);
// 创建两个线程并释放
rt_thread_t tid1 = rt_thread_create("t1", thread_entry1, RT_NULL, 1024, 25, 10);
rt_thread_t tid2 = rt_thread_create("t2", thread_entry2, RT_NULL, 1024, 25, 10);
if (tid1 != RT_NULL)
{
rt_thread_startup(tid1);
rt_kprintf("线程1启动成功! \n");
}
else
{
rt_kprintf("线程1启动失败! \n");
}
if (tid2 != RT_NULL)
{
rt_thread_startup(tid2);
rt_kprintf("线程2启动成功! \n");
}
else
{
rt_kprintf("线程2启动失败! \n");
}
}
```
全局变量必须加互锁;