计算机操作系统13,14
第十三课:线程(Thread)
一、先回顾:什么是进程?
我们已经学过:
进程 = 正在运行的程序。
例如:
你打开:
微信
操作系统会:
创建PCB
↓
分配内存
↓
分配资源
↓
进入就绪队列
↓
开始运行
所以:
微信:
就是:
一个进程。
二、一个问题
假设:
现在:
微信正在运行。
它同时:
聊天
↓
下载图片
↓
播放语音
↓
接收消息
请问:
这些事情:
是不是:
严格一个接一个做?
如果:
下载图片的时候。
聊天必须暂停。
用户会觉得:
微信:
非常卡。
所以:
需要:
同时做很多事情。
三、是不是开很多个进程?
一种办法:
聊天:
一个进程
下载:
一个进程
语音:
一个进程
可以吗?
可以。
但是:
问题来了。
每个进程:
都有:
PCB
内存
页表
文件描述符
资源……
是不是:
太浪费了?
例如:
三个微信进程。
都需要:
加载:
微信程序。
重复占用很多资源。
四、于是:线程诞生了
操作系统设计者想到:
既然这些功能都属于同一个微信,为什么不共享资源?
于是:
把一个进程:
拆成:
很多:
线程(Thread)。
所以:
现在:
微信:
变成:
微信进程
│
├── 聊天线程
├── 下载线程
├── 网络线程
├── 语音线程
└── 界面线程
这些:
都是:
线程。
五、什么是线程?
教材定义:
线程是CPU调度和分派的基本单位。
这一句话:
很多同学第一次看。
完全不知道什么意思。
我们换成人话。
以前:
CPU:
调度:
QQ进程
↓
浏览器进程
现在:
CPU:
调度:
聊天线程
↓
下载线程
↓
播放线程
所以:
以前:
CPU调度:
进程。
现在:
CPU真正调度的是:
线程。
这句话非常重要。
六、为什么线程叫"轻量级进程"?
很多教材会说:
线程是轻量级进程(Lightweight Process)。
为什么?
来看对比。
一个新进程:
需要:
创建PCB
↓
申请内存
↓
建立页表
↓
建立文件表
↓
建立资源
是不是:
很麻烦?
而:
线程:
只需要:
创建TCB(线程控制块)
↓
加入调度
资源:
直接:
共享。
所以:
线程:
创建速度:
非常快。
切换:
也快。
因此:
叫:
轻量级进程。
七、线程共享什么?
这是考试最喜欢问的问题。
假设:
微信:
一个进程。
里面:
五个线程。
它们:
共享:
代码(程序)
数据(全局变量)
堆(Heap)
打开的文件
内存空间
例如:
聊天线程:
下载了一张图片。
下载线程:
放到:
内存。
聊天线程:
立刻:
就能看到。
为什么?
因为:
共享内存。
八、线程独有的是什么?
虽然共享很多东西。
但是:
每个线程:
也必须有自己的内容。
例如:
聊天线程:
运行到了:
第100行代码。
下载线程:
运行到了:
第500行。
是不是:
不能:
共用:
程序计数器?
否则:
CPU不知道:
恢复到哪里。
所以:
每个线程:
都有:
程序计数器(PC)
寄存器
栈(Stack)
线程状态
这些:
不能共享。
九、一张图彻底理解
微信进程
┌────────────────────┐
│ 代码区 │
│ 数据区 │
│ 堆(Heap) │
│ 打开的文件 │
└────────────────────┘
▲ ▲ ▲
│ │ │
┌──────┘ │ └──────┐
│ │ │
┌──────────┐ ┌──────────┐ ┌──────────┐
│聊天线程 │ │下载线程 │ │界面线程 │
│PC │ │PC │ │PC │
│寄存器 │ │寄存器 │ │寄存器 │
│栈 │ │栈 │ │栈 │
└──────────┘ └──────────┘ └──────────┘
记住:
上面共享。
下面独立。
十、为什么线程切换更快?
我们以前学过:
进程切换:
需要:
保存PCB
↓
切换页表
↓
切换地址空间
↓
恢复PCB
很慢。
而:
线程切换。
因为:
共享:
代码
内存
页表
所以:
不用:
重新建立。
只需要:
保存:
线程现场
↓
恢复:
另一个线程现场
因此:
线程切换:
快得多。
十一、进程和线程对比(★★★★★)
这是考试一定会考的表格。
| 比较内容 | 进程 | 线程 |
|---|---|---|
| 定义 | 资源分配的基本单位 | CPU调度的基本单位 |
| 是否拥有独立地址空间 | 有 | 没有(共享进程地址空间) |
| 创建开销 | 大 | 小 |
| 切换开销 | 大 | 小 |
| 通信 | 较复杂(IPC) | 简单(共享内存) |
| 崩溃影响 | 一般不影响其他进程 | 一个线程出错可能导致整个进程崩溃 |
十二、一个非常经典的例子
为什么:
Chrome:
一个浏览器。
打开一个网页。
任务管理器里面:
却看到:
很多线程?
因为:
浏览器:
通常会:
主线程
↓
渲染线程
↓
网络线程
↓
JavaScript线程
↓
GPU线程
大家:
一起工作。
所以:
浏览器:
很流畅。
十三、为什么多线程会带来新问题?
如果:
两个线程:
同时:
修改:
余额
例如:
账户:
100元。
线程A:
取:
50
线程B:
同时:
取:
80
如果:
没有协调。
最后:
余额:
可能:
变成:
50
20
甚至:
-30
都有可能。
为什么?
因为:
两个线程:
同时:
访问:
共享数据。
于是:
新的问题:
出现了。
这就是:
线程同步(Synchronization)
也是操作系统最经典的一章。
“为什么两个线程同时执行,会算错结果?”
如果这一课理解了,后面的:
都会变得非常容易。
这一课我们先不讲任何公式。
只讲:
为什么需要同步?
第一部分:一个银行案例
假设:
你的银行卡里面有:
100 元
现在:
你同时:
微信支付
+
支付宝支付
都要扣:
50 元。
正常结果:
100
↓
50
↓
0
余额:
应该:
0 元
没有问题。
第二部分:真正发生了什么?
其实。
CPU执行:
不是:
一句话。
而是:
很多条指令。
例如:
微信:
实际上:
可能这样。
① 读取余额
② 计算余额
③ 写回余额
支付宝:
也是:
① 读取余额
② 计算余额
③ 写回余额
注意!
这里:
问题来了。
第三部分:最经典的错误
假设:
CPU:
先执行:
微信。
第一步:
余额:
100
微信:
读取:
100。
但是:
还没有:
写回。
CPU:
突然:
切换。
去执行:
支付宝。
支付宝:
读取:
余额。
注意。
此时:
余额:
还是:
100
因为:
微信:
还没:
写回。
于是:
支付宝:
计算:
100-50
=
50
写回。
余额:
变成:
50
然后。
CPU:
又回来。
继续:
微信。
微信:
认为:
余额:
还是:
100。
因为:
它:
之前:
已经:
读出来了。
于是:
微信:
也写回:
50
最后:
余额:
是多少?
50
但是:
明明:
扣了:
两次:
50。
正确答案:
应该:
0
是不是:
出错了?
第四部分:为什么出错?
因为:
两个线程:
同时:
修改:
同一块数据。
这种情况:
叫:
竞争条件(Race Condition)
教材定义:
多个线程(或进程)同时访问共享数据,结果依赖执行顺序。
记住:
关键词:
同时。
第五部分:共享数据叫什么?
例如:
银行卡余额。
多个线程:
都能访问。
这种资源:
叫:
临界资源(Critical Resource)
例如:
银行卡余额
打印机
共享变量
共享文件
共享内存
都是:
临界资源。
为什么?
因为:
一次:
只能:
一个线程:
修改。
第六部分:访问共享资源的代码
来看代码。
balance = balance - 50;
你觉得:
是不是:
一条语句?
实际上:
CPU:
会拆成:
读取 balance
↓
减50
↓
写回 balance
所以:
这一段代码:
必须:
连续执行。
不能:
执行到一半。
被别人:
插进来。
这段代码:
叫:
临界区(Critical Section)
教材定义:
访问临界资源的代码。
记住:
不要混。
很多同学:
分不清。
临界资源 vs 临界区
这是考试最喜欢出的选择题。
| 名称 | 是什么 |
|---|---|
| 临界资源 | 被共享的数据或设备 |
| 临界区 | 操作临界资源的那段代码 |
例如:
余额
属于:
临界资源。
而:
balance = balance - 50;
属于:
临界区。
第七部分:怎么解决?
最简单的方法。
规定:
一次:
只能:
一个线程:
进入。
例如:
微信:
正在:
修改余额。
支付宝:
必须:
外面:
等待。
直到:
微信:
完成。
才能:
进入。
于是:
执行顺序:
变成:
微信
↓
全部执行完成
↓
支付宝
是不是:
就不会:
错?
这就是:
互斥(Mutual Exclusion)
第八部分:什么叫同步?
很多人:
容易混。
其实:
同步:
不是:
一起执行。
而是:
按照规定的顺序执行。
例如:
打印。
必须:
编辑完成
↓
才能打印。
不能:
文件:
还没写好。
打印机:
已经:
开始打印。
这就是:
同步。
第九部分:同步和互斥区别(★★★★★)
这是最容易考的地方。
互斥
两个线程:
抢:
同一个资源。
例如:
微信
↓
余额
↑
支付宝
必须:
一个一个来。
关键词:
竞争。
同步
例如:
生产者:
先生产。
消费者:
后消费。
必须:
有顺序。
关键词:
协作。
一张图理解
互斥:
A ----\
\
临界资源
/
B ----/
同步:
生产
↓
放入缓冲区
↓
消费者取走
一个:
解决:
竞争。
一个:
解决:
先后顺序。
第十部分:进程同步四大原则(★★★★★)
教材:
一定会讲。
必须记住。
① 空闲让进
如果:
没人:
使用。
那么:
别人:
应该:
马上:
进去。
不能:
空着。
② 忙则等待
如果:
已经:
有人:
在里面。
别人:
必须:
等待。
不能:
一起。
③ 有限等待
不能:
一直:
等。
否则:
有人:
永远:
进不去。
就是:
饥饿。
所以:
必须:
保证:
最终:
轮到。
④ 让权等待
如果:
不能:
进入。
不要:
一直:
占CPU。
应该:
睡觉。
等:
别人:
叫醒。
这个原则:
后面:
信号量:
会详细讲。
第十一部分:生活例子
厕所。
只有:
一个坑位。
是不是:
典型:
临界资源?
有人:
进去。
别人:
只能:
外面:
排队。
是不是:
互斥?
等:
里面:
出来。
外面:
进去。
是不是:
同步?
整个过程:
是不是:
满足:
四个原则?
这就是:
操作系统。
第十二部分:为什么现代程序越来越重视同步?
因为:
现在:
CPU:
越来越多核。
例如:
8核
16核
32核
多个线程:
真的:
同时:
运行。
如果:
没有:
同步。
程序:
很容易:
数据错乱。
所以:
现代编程:
几乎:
离不开:
同步。
第十三部分:本课重点(★★★★★)
必须记住
✅ 竞争条件(Race Condition)
多个线程同时访问共享数据,结果依赖执行顺序。
✅ 临界资源
一次:
只能:
一个线程:
访问。
✅ 临界区
访问临界资源:
那段代码。
✅ 互斥
解决:
竞争问题。
一次:
一个线程。
✅ 同步
解决:
协作顺序。
谁先。
谁后。
第十四部分:一句口诀
一定要背下来:
资源叫临界,
代码叫临界区;
抢资源叫互斥,
按顺序叫同步。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)