线程与并发
复习
- 时间片与优先级调度:让多个程序都感觉自己被及时响应
- 多级反馈队列:不知道任务长短时,靠反馈自动调整优先级
- 多核调度:任务分到多个 CPU 后还要处理负载和缓存亲和
TL;DR
- 线程是进程内部的一条执行线,一个进程可以有多条
- 同一进程的线程共享地址空间和资源,各自有寄存器和栈
- 并发是“看起来同时”,并行是“真的同时”
- 线程更轻、切换更省,但共享内存也让它们更容易互相干扰
正文
一个文本编辑器,既要响应你的按键,又要在后台自动保存文件。它是怎么“同时”做两件事的?
你可能会说:开两个进程不就行了。可进程之间是隔离的——共享一段数据要费不少周折,创建和切换的开销也不小。于是,人们想在同一个进程内部,分出几条并行的执行线。
进程里的多条执行线
这个“执行线”,就是线程(thread)。
一个进程可以包含多个线程。它们共享同一个地址空间、同一批打开的文件、同样的全局数据;但每个线程有自己独立的寄存器和栈——因为每条执行线都要记住自己跑到哪了。
进程
├── 地址空间、打开的文件(共享)
├── 线程 1:寄存器 + 栈
├── 线程 2:寄存器 + 栈
└── 线程 3:寄存器 + 栈
好比一间办公室:办公桌、文件柜、打印机(地址空间、资源)是所有员工(线程)共用的;但每个人手头正在做的事(寄存器、栈)各不相同。
并发与并行
这里要分清两个容易混的词:
- 并发(concurrency):多个任务在一段时间内都在推进,看起来像同时,但可能是在一个核上快速切换出来的
- 并行(parallelism):多个任务在同一时刻真的各占一个核,同时执行
一个核也能“并发”——靠时间片切换,你几乎察觉不到;但真正的“并行”,需要多个核。二者不是一回事。
线程的利与弊
线程的好处很直接:
- 轻量:创建、切换只涉及寄存器和栈,比整个进程便宜
- 方便共享:同一进程的线程天然共享数据,不必像进程那样绕来绕去
- 响应好:一个线程在等 I/O 时,别的线程可以继续干活
可共享是一把双刃剑。正因为线程共用同一片内存,它们很容易互相踩脚:两个线程同时改同一个变量,可能算错;一个线程崩溃,整个进程都可能遭殃。
这正是“并发”话题的由来:一旦允许多条执行线共享数据,我们就得想办法让它们不冲突。这部分内容,从下一章起会连着讲好几章。
思考题
一个进程的多个线程共享地址空间,只在寄存器和栈上各自独立。为什么“栈”必须每个线程一份,而不能共享?
小结
知识点
- 线程是进程内的执行线,一个进程可有多个
- 线程共享地址空间和资源,各有寄存器和栈
- 并发是“看起来同时”,并行是“真的同时”
- 线程轻量、便于共享,但共享也带来冲突风险
参考资料
- Wikipedia(zh):线程:线程的概念
- Wikipedia(zh):并发计算:并发与并行的区别
- Wikipedia(zh):并行计算:并行计算
思考题答案(仅供参考)
栈记录的是“这条执行线自己的调用过程”:函数调到哪里、局部变量是什么、返回地址在哪。每条执行线走的函数、想返回的地方都不一样,必须各存一份,否则两条线会互相覆盖对方的返回地址,一返回就跳错地方。而堆、全局数据这类“大家都要用”的内容,才适合共享。所以“共享地址空间、各自独立栈”的分工,恰好对应了“资源共用、执行独立”。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪