计算机操作系统15
第十五课:临界区问题(Critical Section Problem)
一、什么是临界区问题?
上一课我们学了:
例如:
balance = balance - 50;
这段代码:
就是:
临界区。
现在:
两个线程:
都想:
进入。
怎么办?
操作系统:
必须:
决定:
谁先进去?
谁在外面等?
这就是:
临界区问题(Critical Section Problem)
一句话总结:
如何让多个进程(或线程)安全地访问临界资源。
二、一个错误的方法——关闭中断
有同学会想到:
CPU 不就是因为切换线程才出错的吗?
那我:
关闭中断
↓
CPU就不会切换
是不是:
就不会:
出错?
例如:
进入临界区
↓
关闭中断
↓
修改余额
↓
打开中断
听起来很好。
但是:
实际上:
几乎不用。
为什么?
第一:
用户程序:
不能:
随便关闭中断。
否则:
任何程序:
都可以:
让电脑:
失去响应。
这是非常危险的。
第二:
多核CPU。
假设:
有:
CPU1
CPU2
CPU1:
关闭了:
中断。
CPU2:
还能:
继续:
修改余额。
所以:
还是:
出错。
因此:
关闭中断:
不是通用方案。
三、第二种方法——忙等待(Busy Waiting)
假设:
门口:
挂着:
一个牌子。
busy = false
意思:
没人。
于是:
线程A:
看到:
没人。
进去。
然后:
改成:
busy = true
线程B:
来到。
发现:
busy = true
就在门口:
一直:
检查。
while(busy){
一直等
}
这种方式:
叫:
忙等待(Busy Waiting)
什么叫忙等待?
不是:
睡觉。
而是:
CPU:
一直:
执行:
while(true)
不停:
问:
好了没?
好了没?
好了没?
CPU:
一直:
空转。
忙等待有什么缺点?
例如:
线程A:
进入:
临界区。
需要:
10秒。
线程B:
这10秒:
一直:
循环。
CPU:
是不是:
白白:
浪费了?
所以:
忙等待:
效率:
很低。
四、为什么"检查 busy"也会出错?
很多同学第一次会说:
“busy 不就一个变量吗?”
问题来了。
假设:
开始:
busy = false
线程A:
执行:
if(!busy)
CPU:
刚判断:
没人。
准备:
进入。
突然:
切换。
线程B:
也判断:
busy == false
于是:
两个线程:
一起:
进去。
是不是:
又失败了?
所以:
判断和修改不能分开。
五、原子操作(Atomic Operation)
于是:
提出一个概念:
原子操作(Atomic Operation)
什么叫原子?
来自:
物理学。
意思:
不能再分。
在操作系统中:
表示:
一个操作执行过程中,不会被中断。
例如:
判断busy
+
修改busy
必须:
一次:
完成。
不能:
执行一半。
六、锁(Lock)的思想
于是:
操作系统:
发明:
锁。
生活中:
像这样:
房门
↓
钥匙
只有:
拿到钥匙。
才能:
进去。
别人:
只能:
等。
程序:
也是:
一样。
进入:
临界区。
第一步:
加锁
出来:
解锁
于是:
流程:
加锁
↓
进入临界区
↓
修改数据
↓
解锁
是不是:
很像:
厕所:
锁门?
七、进入区、临界区、退出区、剩余区
教材中经常会出现四个区域。
一定要会区分。
┌──────────────┐
│ 进入区 │
├──────────────┤
│ 临界区 │
├──────────────┤
│ 退出区 │
├──────────────┤
│ 剩余区 │
└──────────────┘
① 进入区(Entry Section)
作用:
申请进入。
例如:
加锁
检查busy
都是:
进入区。
② 临界区(Critical Section)
真正:
修改:
共享数据。
例如:
余额--
库存--
打印
③ 退出区(Exit Section)
作用:
释放资源。
例如:
busy=false
解锁
让:
别人:
进去。
④ 剩余区(Remainder Section)
剩下:
普通代码。
例如:
打印欢迎信息
计算数学题
播放音乐
和:
共享资源:
没关系。
八、临界区问题的三个要求(★★★★★)
这是考试高频考点。
① 互斥(Mutual Exclusion)
任何时候:
最多:
一个线程:
进入:
临界区。
这是:
最基本要求。
② 空闲让进(Progress)
如果:
没人:
进入。
别人:
应该:
立刻:
进去。
不能:
一直:
卡住。
③ 有限等待(Bounded Waiting)
不能:
永远:
等。
例如:
线程A:
进去:
1000次。
线程B:
一次:
都没进去。
这就是:
饥饿。
必须:
保证:
最终:
能进入。
九、教材里的经典图
线程A
进入区
↓
临界区
↓
退出区
↓
剩余区
────────────
线程B
进入区
↓
临界区
↓
退出区
↓
剩余区
只有:
临界区
不能:
同时:
进入。
其它区域:
可以:
并发执行。
十、为什么还要继续学习?
到目前为止。
我们知道:
需要:
互斥
↓
加锁
↓
解锁
但是:
真正的问题来了。
这个:
锁。
到底:
怎么实现?
程序:
怎么知道:
什么时候:
等待?
什么时候:
唤醒?
这就是:
下一课:
信号量(Semaphore) 。
十一、本课重点总结
必须掌握
✅ 临界区问题:如何让多个线程安全访问临界资源。
✅ 原子操作:执行过程中不可中断。
✅ 锁(Lock):保证同一时刻只有一个线程进入临界区。
✅ 四个区域:
进入区
↓
临界区
↓
退出区
↓
剩余区
三大要求(★★★★★)
牢记:
- 互斥 :同一时刻只能有一个线程进入临界区。
- 空闲让进(Progress) :临界区空闲时,应允许一个等待线程进入。
- 有限等待(Bounded Waiting) :等待时间必须有上界,避免饥饿。
注意:上一课讲的"让权等待"是 同步机制设计的一般原则 ;而教材在"临界区问题"这里通常强调的是 互斥、空闲让进、有限等待 这三个要求。不要混淆。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)