1:信号量的基本概念:
信号量(Semaphore)是一种用于控制多线程或多进程间同步访问共享资源的机制,由荷兰计算机科学家Edsger Dijkstra于1965年提出。其核心功能是通过计数器限制同时访问资源的线程/进程数量,避免竞争条件(Race Condition)。
信号量的类型
-
二进制信号量
- 计数器值为0或1,适用于互斥锁场景(类似互斥锁,但信号量可由非持有者释放)。
- 示例:保护临界区,一次仅允许一个线程进入。
-
计数信号量
- 计数器值为非负整数,限制资源的并发访问数量。
- 示例:数据库连接池管理,限制最大连接数。
二进制信号量:
0:表示没有积累下来的release 释放信号量操作,且可能存在该信号量已造成线程阻塞。
1:表示有1个或者多个release 释放信号量操作。
计数信号量:
计数信号量在开始我们设计好个数完成工作;例如:停车场车位,如果有车位,线程可以启动,没有则要等其他车(线程)释放车位(信号量)。
信号量的特征:
以同步为目的的信号量和以互斥为目的的信号量在使用有如下不同:
用作互斥时,信号量创建后可用信号量个数应该是满的,线程在需要使用临界资源时,先获取信号量,使其变空,这样其他线程需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界资源的安全。但是这样子有一个缺点就是有可能产生优先级翻转,优先级翻转的危害具体会在互斥量章节中详细讲解。
用作同步时,信号量在创建后被置为空,线程1取信号量而阻塞,线程2在某种条件发生后,释放信号量,于是线程1得以进入就绪态,如果线程1的优先级是最高的,那么就会立即切换线程,从而达到了两个线程间的同步。同样的,在中断服务函数中释放信号量,也能达到线程与中断间的同步。
信号量的运行机制:
二进制信号量:
1:先创建二值信号量,最大可用信号量为1个;
2:信号量获取,从创建的信号量获取一个信号量,获取成功线程启动,获取失败进入阻塞状态。
3:进入阻塞状态,等待信号量的释放。
计数信号量:
从创建的信号量获取一个信号量,获取成功线程启动,直到所有信号量都被获取,获取失败进入阻塞状态等待释放出来空的信号量。
如图所示:
常用信号量函数及作用:
rt_sem_create:信号量创建。
rt_sem_delete:信号量删除。
rt_sem_release:信号量释放。
rt_sem_take:信号量获取。
信号量的应用:
无论使用哪种信号量都要先定义句柄,然后启动线程。利用消息队列来实现内容的输出;
根据上述学习我们试着完成信号量相关工程创建:这里跳过stm32配置,只指导代码和思路:
要求:
- 创建按键有效标志信号量,完成按键扫描线程和按键功能线程的之间的同步。
- 按下按键1,进行计数型信号量获取,模拟停车场停车操作,其等待时间是0,在串口调试助手输出相应信息。
- 按下按键2,进行计数型信号量的释放,模拟停车场取车操作,在串口调试助手输出相应信息。
1:定义信号量句柄:
static rt_sem_t test_sem=RT_NULL;
2:书写信号量队列并启动:
test_sem=rt_sem_create("test_sem",1, RT_IPC_FLAG_FIFO);
3:完成实验目的的主函数代码以及注释:
//定义消息队列控制块
static rt_mq_t test_mq=RT_NULL;
//定义信号量控制块
static rt_sem_t test_sem=RT_NULL;
//定义按键标志信号量控制块
static rt_sem_t KeyFlag_sem=RT_NULL;
//定义停车位信号量控制块
static rt_sem_t Stopcar_sem=RT_NULL;
//定义线程控制块
static rt_thread_t receive_thread=RT_NULL;
static rt_thread_t send_thread=RT_NULL;
//定义全局变量
uint8_t ucValue[2]={0x00,0x00};
static void receive_thread_entry(void*parameter);
static void send_thread_entry(void*parameter);
rt_kprintf("这是一个模拟停车位实验\n");
rt_kprintf("receive线程接收到的消息在串口显示\n");
//创建消息队列
test_mq = rt_mq_create("test_mq",40,20,RT_IPC_FLAG_FIFO);
//创建信号量1
test_sem=rt_sem_create("test_sem",1,RT_IPC_FLAG_FIFO);
//创建信号量2
KeyFlag_sem=rt_sem_create("KeyFlag_sem",1,RT_IPC_FLAG_FIFO);
//创建信号量3
Stopcar_sem=rt_sem_create("Stopcar_sem",5,RT_IPC_FLAG_FIFO);
4:接收发线程创建和启动这里不重复教学了。如下图创建:
5:对接收发函数进行定义完成实验要求(这里按键函数省略在仓库有完整代码):
static void send_thread_entry(void*parameter)
{
rt_err_t uwRet=RT_EOK;
while(1)
{
if((KeyFlag==1)&&(KEY_Value==2))
{
KeyFlag=0;
rt_kprintf("可用的车位数目: %d r\n",Stopcar_sem->value);
uwRet=rt_sem_release(Stopcar_sem);
if(RT_EOK==uwRet)
{
rt_kprintf("按键2被按下:释放一个停车位 \r\n");
rt_kprintf("可用的车位数目: %d r\n",Stopcar_sem->value);
}
else
{
rt_kprintf("按键2被按下:没有停车位可以释放! \r\n");
}
}
rt_thread_delay(20);
}
}
static void receive_thread_entry(void*parameter)
{
rt_err_t uwRet=RT_EOK;
while(1)
{
if((KeyFlag==1)&&(KEY_Value==1))
{
KeyFlag=0;
rt_kprintf("可用的车位数目: %d r\n",Stopcar_sem->value);
uwRet=rt_sem_take(Stopcar_sem,0);
if(RT_EOK==uwRet)
{
rt_kprintf("按键1被按下:成功申请到车位 \r\n");
rt_kprintf("可用的车位数目: %d r\n",Stopcar_sem->value);
}
else
{
rt_kprintf("按键1被按下:不好意思,现在车位已经满了! \r\n");
}
}
rt_thread_delay(20);
}
}
6:测试结果:

