同步与互斥(3)--信号量机制

操作系统|信号量机制

前文我们讲完了软件、硬件两大类互斥实现方案,但不管是 Peterson 算法,还是 TSL、开关中断,全都卡在无法满足「让权等待」 这一条核心原则:进程拿不到锁时会循环忙等,死死占用 CPU 空耗算力。 而信号量机制 是操作系统标准解法,分为整型信号量记录型信号量两代,最终记录型信号量可以完整满足互斥四大准则,完美实现进程互斥与同步,也是 PV 操作经典题型的底层根基。

一、信号量基础概念铺垫

1. 信号量是什么

信号量本质就是用来统计系统可用资源数量的变量

  • 数值 = 当前还剩下多少份可用资源;
  • 系统严格规定:信号量仅能执行三种操作:初始化、P 操作(申请资源)、V 操作(释放资源),不允许程序随意直接修改变量数值,从根源避免并发错乱。

2. PV 原语(Wait / Signal)

P 操作 = wait(S):向系统申请占用一份资源 ,对应进入区上锁; V 操作 = signal(S):使用完毕归还释放一份资源 ,对应退出区解锁。 P、V 属于原语,执行过程关中断、一气呵成不可被打断,杜绝多进程并发修改导致逻辑出错。

词源小知识:P 来自荷兰语proberen(尝试、申请),V 来自verhogen(增加、释放)。

二、第一代:整型信号量

1. 结构定义

只用一个单独整型变量S.value代表剩余资源总数,仅依靠循环判断完成 P/V:

复制代码
// P操作 申请资源
while(S.value <= 0);  // 资源不足就一直循环轮询
S.value = S.value - 1;

// V操作 释放资源
S.value = S.value + 1;

2. 举例

公寓里有 1 台公用洗衣机(初始化 S=1)。 租客想洗衣服时执行 P 操作:如果洗衣机有人用(S≤0),就站在洗衣机门口不停反复查看、原地打转;直到别人用完 S>0,立刻占用洗衣机。 用完执行 V,把资源数 + 1 归还。

3. 缺陷

完全不满足「让权等待」原则 。 资源被占用时,进程不会放弃 CPU、不会进入阻塞休眠,而是无限 while 循环轮询查询状态,也就是忙等,CPU 大量算力浪费在无效循环上,系统资源利用率极低。

三、第二代:记录型信号量(

为了解决整型信号量忙等问题,给信号量新增阻塞等待队列,拿不到资源的进程直接休眠阻塞、让出 CPU,完美补齐四大互斥原则最后一块短板。

1. 结构体完整定义

复制代码
typedef struct{
    int value;          // 剩余可用资源数量
    struct process *L;  // 阻塞等待该资源的进程队列
} semaphore;
  • value:资源计数器,正数 = 空闲资源;负数绝对值 = 当前正在排队等待的进程个数;
  • L:队列,存放所有因缺资源而被挂起阻塞的进程。

2. P 操作(Wait 申请资源)

复制代码
void wait(semaphore S){
    S.value --;          // 先主动扣减一份资源额度
    if(S.value < 0){     // 扣完后资源不够,需要阻塞
        block(S.L);      // 进程自身进入阻塞态,插入等待队列,让出CPU
    }
}

执行步骤拆解

  1. 先把剩余资源数 - 1,代表我要占用一份;
  2. 如果减完之后value<0,说明原本没有空闲资源,当前进程必须阻塞休眠,挂进信号量的等待队列,主动放弃处理机,不再占用 CPU 轮询。

3. V 操作(Signal 释放资源)

复制代码
void signal(semaphore S){
    S.value ++;                  // 先归还一份资源,计数器+1
    if(S.value <= 0){            // 还有进程在队列里排队等待
        wakeup(S.L);            // 从等待队列队首唤醒一个进程,转为就绪态
    }
}

执行步骤拆解

  1. 使用完毕,资源计数器 + 1,归还占用的资源;
  2. 如果value≤0,代表还有进程卡在队列里等待资源,操作系统唤醒队首一个阻塞进程,让它进入就绪队列等待调度上 CPU。

4. 举例

依旧是公寓 1 台洗衣机,初始化S.value=1,等待队列为空。

  1. 租客 A 执行 P:value变为 0,没有小于 0,直接使用洗衣机;
  2. 租客 B 紧接着执行 P:value变成 - 1,触发block,B 不再站门口死等,回房间睡觉(阻塞),排入等待队列;
  3. A 洗完执行 V:value变回 0,此时value≤0,系统叫醒队列里的 B,B 从阻塞变就绪,后续调度使用洗衣机。

优势:拿不到资源的进程直接休眠,彻底告别忙等,CPU 可以调度其他无关进程执行,完全符合让权等待; 同时天然满足空闲让进、忙则等待、有限等待,

四、关键数值规律

  1. 初始化S=n:代表系统一共有n 份同类临界资源
  2. P 一定先value--,V 一定先value++,顺序绝对不能颠倒;
  3. S.value > 0:正数 = 当前还有多少空闲资源;
  4. S.value < 0:负数的绝对值 = 此刻正在等待队列里排队阻塞的进程数量;
  5. V 操作只有value++之后依旧≤0,才需要唤醒等待队列里的进程。

五、信号量两大核心用途

1. 实现进程互斥

互斥场景:同一资源同一时间只允许 1 个进程访问,信号量初值固定设为 1(二元信号量 / 互斥锁)。 模板写法:

复制代码
semaphore mutex = 1;
// 进程进入临界区前
P(mutex);
临界区代码;
// 退出临界区后
V(mutex);

2. 实现进程同步

同步场景:约束多个进程固定先后执行顺序,信号量初值一般设为 0。 比如必须 A 进程先执行完,B 才能运行:

  • 初始化S=0
  • A 进程末尾写V(S)
  • B 进程开头写P(S) B 会一直阻塞,直到 A 执行 V 释放信号量,B 才可以继续往下走,强制绑定执行先后顺序。

六、两代信号量核心对比

类型 数据结构 拿不到资源时行为 是否忙等 是否满足四大互斥原则
整型信号量 仅单个 int 变量 while 循环轮询 缺少让权等待,不完整
记录型信号量 资源计数 + 阻塞队列 进程阻塞休眠、让出 CPU 四条原则全部满足,标准方案

七、总结

  1. 信号量本质是资源计数器,P 申请减一,V 释放加一,PV 属于不可中断原语;
  2. 整型信号量存在忙等缺陷,记录型信号量新增等待队列,阻塞进程彻底解决 CPU 空转问题;
  3. P 先减后判断阻塞,V 先加后判断唤醒,牢记执行顺序是做题核心;
  4. 初值为 1 用于互斥,初值为 0 多用于同步,记录型信号量是操作系统解决进程同步互斥的通用底层机制。
相关推荐
Funing71 天前
FreeRTOS 学习复盘(四):事件组、软件定时器与任务通知机制
操作系统·freertos·嵌入式软件
charlie1145141914 天前
Cinux: 为大内核铺路
开发语言·c++·操作系统·现代c++
饼干哥哥4 天前
每晚300+个爆款疯狂跑,0人值守TikTok工厂开张!!
后端·操作系统·产品
小宇子2B4 天前
文件页怎么回收:page cache、干净页和脏页
操作系统
m0_547486665 天前
《系统级编程及分布式应用实现技术》 全套PPT课件
操作系统·系统级编程
小宇子2B5 天前
匿名页怎么回收:反向映射、swap entry、swap cache 和换入
操作系统
小宇子2B5 天前
区间树:反向映射的前置数据结构
操作系统
半条-咸鱼5 天前
【FreeRTOS】核心原理与实战速查手册
c语言·操作系统·rtos
花花无缺6 天前
Windows 定时执行 Python 脚本方案
python·操作系统·命令行