信号量(sem)
信号量可以理解为电影院的座位数(资源数量),当所有座位都满了,新的观众(线程)就需要等待前面的观众离开(释放资源)才能进入。信号量通过控制资源数量来管理多个线程对资源的访问。
信号量的工作机制
信号量的核心思想是控制资源的访问数量。信号量的值代表了可以访问的资源数量。
(1)获取信号量(rt_sem_take):当资源可用时,线程可以获取信号量,信号量的值减1。如果信号量的值为0,线程就会等待,直到资源释放。
(2)释放信号量(rt_sem_release):使用完资源后,线程释放信号量,信号量的值加1,允许其他线程访问资源。
信号量应用实例
#include <rtthread.h>
//创建信号量
rt_thread_t sem = RT_NULL;
//线程1:等待进入电影院
void sem_thread_entry1(void *parameter)
{
rt_kprintf("线程1:等待进入电影院\n");
/*
参数RT_WAITING_FOREVER意为无线等待
作用是永久阻塞,直到获取信号量或被终止
*/
rt_sem_take(sem,1,RT_WAITING_FOREVER);//尝试获取信号量
rt_kprintf("线程1:进入电影院\n");
}
//线程2:离开电影院
void sem_thread_entry2(void *parameter)
{
rt_kprintf("线程2:离开电影院\n");
rt_sem_release(sem);//释放信号量
}
int main(void)
{
//初始化信号量,初始值为1,表示电影院有一个座位
sem = rt_sem_create("sem",1,RT_IPC_FLAG_PRID);
//创建两个线程
rt_thread_t tid1 = rt_thread_create("sem_tid1",sem_thread_entry1,RT_NULL,1024,25,10);
rt_thread_t tid2 = rt_thread_create("sem_tid2",sem_thread_entry2,RT_NULL,1024,25,10);
//启动线程
rt_thread_startup(sem_tid1);
rt_thread_startup(sem_tid2);
return 0;
}
解释:
sem_thread_entry1:线程等待进入电影院,需要等到有空座位(信号量>0)。
sem_thread_entry2:线程模拟一个观众离开电影院,释放一个座位,其他等待的线程可以进入。
相关API
1.rt_sem_create:用于动态创建信号量
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);
参数说明:
name:信号量的名字(可以为RT_NULL表示匿名信号量)
value:信号量的初值,表示信号量当前持有的资源数量(例如初始值为0表示等待事件,为1表示互斥量,或者更大值表示资源计数)
flag:IPC对象的属性标志,常用值:
RT_IPC_FLAG_PRID:优先级等待方式,等待线程按照优先级顺序排列
RT_IPC_FLAG_FIFO:先入先出等待方式,等待线程按照进入顺序排列
返回值:
成功:返回信号量的句柄rt_sem_t
失败:返回RT_NULL
示例:
//创建一个初始值为0的信号量,用于同步场景
rt_sem_t sync_sem = rt_sem_create("sync_sem",0,RT_IPC_FLAG_PRID);
2.rt_sem_init:用于静态信号量的初始化,一般用于静态分配内存的场景
rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value,rt_uint8_t flag);
参数说明:
sem:信号量对象的指针(需要预先定义好该信号量结构体)
name:信号量的名字
value:信号量的初值
flag:IPC对象的属性标志,如rt_sem_create
返回值:
成功:返回RT_EOK
失败:返回相应的错误代码
示例:
//静态信号量定义
static struct rt_semaphore static_sem;
//静态信号量初始化,初始值为1,用于互斥操作
rt_sem_init(&static_sem,"static_sem",1,RT_IPC_FLAG_PRID);
3.rt_sem_take:用于获取信号量,线程会尝试获取信号量,如果信号量的计数值为0,线程会进入等待状态,直到信号量被释放或超时。
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time);
参数说明:
sem:信号量的句柄
time:超时时间(单位为系统tick)表示最大等待时间,如果设置为RT_WAITING_FOREVER,系统会一直等待
返回值:
成功:返回RT_EOK
失败:返回-RT_ETIMEOUT(超时)或其他错误代码
示例:
//线程1等待信号量,直到收到信号
rt_sem_take(sem_proc,RT_WAITING_FOREVER);
4.rt_sem_release:用于释放信号量,增加信号量的计数值,并唤醒等待该信号量的线程
rt_err_t rt_sem_release(rt_sem_t sem);
参数说明:
sem:信号量的句柄
返回值:
成功:返回RT_EOK
失败:返回相应的错误代码
示例:
//线程2完成任务后释放信号量,通知线程1
rt_sem_release(sync_sem);
5.rt_sem_delete:用于删除动态信号量(仅限于rt_sem_create创建的动态信号量)
rt_err_t rt_sem_delete(rt_sem_t sem);
参数说明:
sem:信号量的句柄
返回值:
成功:返回RT_EOK
错误:返回相应的错误代码
示例:
//删除动态创建的信号量
rt_sem_delete(sync_sem);
开发场景
在RTT中,信号量(sem)是一种常见的同步机制,主要用于任务之间的同步与互斥,它可以通过信号量的计数机制来协调多个线程的运行,确保资源的安全访问,信号量的开发场景通常有以下几种:
一、任务同步
多个任务之间需要在某些时间点进行同步时,可以使用信号量。信号量可以用来实现线程间的等待和通知机制。例如,当一个任务完成某项工作后,发送一个信号量,通知等待该信号量的任务继续执行。
应用场景示例:
(1)一个任务处理传感器数据,处理完成后通知另一个任务进行数据发送。
(2)一个任务等待外部事件(如外部中断)发生,中断发生后释放信号量,任务获取信号量后继续执行。
任务同步示例:
假设有两个线程tid1和tid2,tid1需要等待tid2完成某些操作后再继续执行,使用信号量来实现同步。
#include <rtthread.h>
#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5
static rt_thread_t thread1_handle = RT_NULL;
static rt_thread_t thread2_handle = RT_NULL;
static rt_sem_t sync_sem = RT_NULL;
//线程1入口函数
static void thread1_entry(void *parameter)
{
rt_kprintf("线程1;等待线程2发送信号...\n");
rt_sem_take(sync_sem,RT_WAITING_FOREVER);
rt_kprintf("线程1:收到线程2的信号,继续执行...\n");
}
//线程2入口函数
static void thread2_entry(void *parameter)
{
rt_kprintf("执行一些操作...\n");
//模拟耗时操作
rt_thread_mdelay(2000);
rt_thread("线程2:操作完成,发送信号给线程1...\n");
//释放信号量,通知线程1
rt_sem_release(sync_sem);
}
int main(void)
{
//创建一个信号量,初始值为0
sync_sem = rt_sem_create("sync_sem",0,RT_IPC_FLAG_PRID);
if(sync_sem == RT_NULL)
{
rt_kprintf("信号量创建失败\n");
return -1;
}
//创建线程1
thread1_handle = rt_thread_create("thread1",
thread1_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIOITY,
THREAD_TIMESLICE);
if(thread1_handle != RT_NULL)
rt_thread_startup(thread1_handle);
//创建线程2
thread2_handle = rt_thread_create("thread2",
thread1_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIOITY,
THREAD_TIMESLICE);
if(thread2_handle != RT_NULL)
rt_thread_startup(thread2_handle);
return 0;
}
二、任务互斥
当多个任务需要访问共享资源时,可以使用信号量来实现互斥访问。互斥信号量(mutex)可以保证同一时刻只有一个任务能够访问共享资源,避免出现竞争条件和数据不一致的情况。
应用场景示例:
(1)多个任务访问同一个硬件外设(如串口、I2C总线)时,通过信号量保证同一时刻只有一个任务访问外设,防止访问冲突。
(2)多个任务需要访问同一块内存数据时,使用信号量确保数据访问的独占性
任务互斥示例:
当两个线程需要互斥访问共享资源时,可以使用信号量来确保同一时刻只有一个线程能够访问资源
#include <rtthread.h>
#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_sem_t mutex_sem = RT_NULL;
//模拟共享资源
static int shared_resource = 0;
//线程1入口函数
static void thread1_entry(void *parameter)
{
while(1)
{
//获取互斥信号量
rt_sem_take(mutex_sem, RT_WAITING_FOREVER);
rt_kprintf("线程1:访问共享资源,当前值为:%d\n", shared_resource);
shared_resource++;
rt_kprintf("线程1:修改共享资源,值变为:%d\n", shared_resource);
//模拟资源处理耗时
rt_thread_mdelay(1000);
//释放互斥信号量
rt_sem_release(mutex_sem);
//让出CPU给其他线程
rt_thread_mdelay(500);
}
}
//线程2入口函数
static void thread2_entry(void *parameter)
{
while(1)
{
//获取互斥信号量
rt_sem_take(mutex_sem, RT_WAITING_FOREVER);
rt_kprintf("线程2:访问共享资源,当前值为:%d\n", shared_resource);
shared_resource++;
rt_kprintf("线程2:修改共享资源,值变为:%d\n", shared_resource);
//模拟资源处理耗时
rt_thread_mdelay(1000);
//释放互斥信号量
rt_sem_release(mutex_sem);
//让出CPU给其他线程
rt_thread_mdelay(500);
}
}
int main(void)
{
//创建一个互斥信号量,初始值为1
mutex_sem = rt_sem_create("mutex_sem", 1, RT_IPC_FLAG_FIFO);
if(mutex_sem == RT_NULL)
{
rt_kprintf("创建互斥信号量失败\n");
return -1;
}
//创建线程1
tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, 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);
}
return 0;
}
三、事件触发
某些外部事件发生后,需要通知任务执行相应的处理逻辑。这种场景下,外部中断或事件处理函数可以通过释放信号量来通知任务进行处理。任务会等待信号量,当信号量被释放时,任务被唤醒并开始执行。
应用场景示例:
(1)一个任务等待按键时间,当按键被按下时,中断处理程序释放信号量,任务获取信号量并执行按键响应逻辑。
(2)网络模块等待数据接收时间,当网络数据到达时,触发信号量,通知任务进行数据处理。
事件触发示例:
#include "rtthread.h"
#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5
static rt_thread_t thread_handle = RT_NULL;
static rt_sem_t event_sem = RT_NULL;
//模拟外部事件发生,例如按键中断
void external_event_handler(void)
{
rt_kprintf("外部事件发生!发送信号量...\n");
//释放信号量,通知等待线程
rt_sem_release(event_sem);
}
//线程入口函数
static void thread_entry(void *parameter)
{
while (1)
{
rt_kprintf("等待外部事件...\n");
//等待外部事件的信号量
rt_sem_take(event_sem, RT_WAITING_FOREVER);
rt_kprintf("线程:收到外部事件信号,处理事件...\n");
//模拟处理事件过程
rt_thread_mdelay(1000);
}
}
int main(void)
{
//创建一个信号量,初始值为0
event_sem = rt_sem_create("event_sem", 0, RT_IPC_FLAG_FIFO);
if (event_sem == RT_NULL)
{
rt_kprintf("创建信号量失败!\n");
return -1;
}
//创建线程
thread_handle = rt_thread_create("thread", thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
if (thread_handle != RT_NULL)
{
rt_thread_startup(thread_handle);
}
//模拟外部事件
rt_thread_mdelay(2000);
external_event_handler();
return 0;
}
四、限量资源管理
信号量可以用来管理有限的资源,信号量的计数值可以表示可用资源的数量。当一个任务获取到资源时,信号量的计数值减少;当任务释放资源时,信号量的计数值增加。如果信号量的计数值为0,则任务必须等待资源可用。
应用场景示例:
(1)一个系统有多个任务需要访问一个有限的资源池(如线程池,内存池),信号量可以表示可用资源的数量,确保任务只能在资源可用时进行访问。
(2)限制并发访问的数量,比如同一时刻只能有固定数量的任务访问某个服务。
限量资源管理示例:
#include <rtthread.h>
#define THREAD_STACK_SIZE 512
#define THREAD_PRIORITY 10
#define THREAD_TIMESLICE 5
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_sem_t res_sem = RT_NULL;
//线程1入口函数
static void thread1_entry(void *parameter)
{
while(1)
{
//尝试获取资源
if(rt_sem_take(res_sem, RT_WAITING_FOREVER) == RT_EOK)
{
rt_kprintf("线程1:成功获取资源!\n");
//模拟使用资源
rt_thread_mdelay(2000);
//释放资源
rt_sem_release(res_sem);
rt_kprintf("线程1:释放资源!\n");
}
//让出CPU给其他线程
rt_thread_mdelay(500);
}
}
//线程2入口函数
static void thread2_entry(void *parameter)
{
while(1)
{
//尝试获取资源
if(rt_sem_take(res_sem, RT_WAITING_FOREVER) == RT_EOK)
{
rt_kprintf("线程2:成功获取资源!\n");
//模拟使用资源
rt_thread_mdelay(2000);
//释放资源
rt_sem_release(res_sem);
rt_kprintf("线程2:释放资源!\n");
}
//让出CPU给其他线程
rt_thread_mdelay(500);
}
}
int main(void)
{
//创建一个信号量,初始值为1,表示有一个可用资源
res_sem = rt_sem_create("res_sem", 1, RT_IPC_FLAG_FIFO);
if(res_sem == RT_NULL)
{
rt_kprintf("创建信号量失败!\n");
return -1;
}
//创建线程1
tid1 = rt_thread_create("thread1", thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, 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);
}
return 0;
}