操作系统 并发
本文将介绍操作系统对并发的支持。
一、进程与线程
作者:pansz
链接:https://www.zhihu.com/question/19901763/answer/13299543
- 单进程单线程:一个人在一个桌子上吃菜
- 单进程多线程:多个人在同一个桌子上一起吃菜
- 多进程单线程:多个人每个人在自己的桌子上吃菜
1. 进程
在操作系统中,一个运行中的任务通常对应一个进程。
进程通常包含以下三个特征:
独立性:进程是系统中的独立单位,具有独立的资源、私有的空间
动态性:进程具有自己的生命周期
并发性:多个进程可以并发执行而不会相互影响
并发与并行并不相同
- 并发:同一时刻只能有一条指令执行,当多个进程的指令快速轮换执行,从而达到宏观上同时执行的效果
- 并行:同一时刻多条指令在多个处理器上同时执行
2. 线程
- 线程是比进程更小的执行单位
- 线程被包含在进程中,是进程中的实际执行单位
- 系统在切换线程时,与切换进程相比,负担更小,因此线程也被称为轻量级进程
3. 多进程
多进程就是计算机同时执行多个进程(软件),现代操作系统均支持多进程,用户可以随意开启多个软件,并让它们同时运行在计算机之上。
对于 CPU 而言,它在同一时间只能执行一个程序。因此 CPU 会在多个进程之间轮换执行,从而达到 “同时运行” 的效果。
4. 多线程
(1) 什么是多线程?
多线程扩展了多进程的概念,使得一个进程可以同时并发处理多个任务。
(2) 特点
- 线程是进程的组成部分
- 进程中必须有一个主线程
- 进程中可以有很多个线程
- 当进程被创建,主线程也就被创建了
- 线程的执行是抢占式的
- 与进程相比,线程之间的分隔程度更小,它们共享进程的状态
(3) 线程与进程的相同点
线程之于进程,就像进程之于操作系统。
线程在进程中是独立的
进程在操作系统中也是独立的
线程具有生命周期
进程具有生命周期
线程可以并发执行
进程可以并发执行
(4) 多线程的优势
- 线程可以共用进程的状态,因此具有更好的性能
- 线程会共享进程的状态,因此更容易实现相互通信
二、为什么在操作系统中学习并发?
因为 “历史”。
操作系统是第一个并发程序,它需要借助并发技术保证系统的正确运行。
三、并发带来的问题
具体请看:
四、锁
1. 什么是锁?
操作系统提供锁这一概念。
操作系统允许进程通过 lock()
方法获取锁,并通过 unlock()
方法释放锁,有且仅有获取到锁的线程才能访问到临界区。
锁为程序员提供了最小程度的调度控制,通过锁,原本由操作系统调度的混乱状态变得可控。
2. 锁的基本要求
- 互斥:最基本要求;确保锁有效
- 公平:保证尝试获取锁的线程都有公平的机会获取到锁
- 性能:避免使用锁后增加性能开销
3. 互斥的解决方案
(1) 控制中断
“控制中断” 是最早的一个互斥的解决方案,即在进入临界区之前关闭中断,保证临界区中的代码原子性执行,在进入临界区之后开启中断,操作系统正常运行。
这一方法的优点是:
- 简单
这一方法的缺点是:
会使所有线程都有机会获得 “不用中断” 这一特权
可能会有恶意程序通过这一漏洞独占处理器
不支持多处理器
如果有多个处理器,则可以有多个进程进入临界区,虽然它们不会中断,但同样会产生并发问题
(2) 以变量为锁
简单来说,就是用一个变量来标志锁是否被线程占用,伪代码如下:
1 |
|
这一方法的缺点是:
操作不是原子的,在并发的情况下,可能会出现多个线程同时加锁成功的情况
因为判断条件和更新变量是两步操作
有性能问题
线程在等待锁时会循环等待
假设线程 1 获取到锁,线程 2、3、4 均在尝试获取锁,在公平调度的假设下,CPU 只有 1/4 的时间在工作,另外 3/4 的时间在循环等待,这将造成极大的性能损失。
(3) 保证互斥 - 硬件支持
以变量为锁 + 硬件支持
一些操作系统提供了 “比较并交换” 指令,它能够将 判断条件; 更新变量
这两步操作合并为一步操作,从而能够正确保证互斥。
(4) 减少性能损失 - 线程主动放弃
操作系统可以提供一个 API,其作用是主动放弃 CPU,线程可以在发现尝试加锁发现锁已被占用时调用该方法,从而避免了循环等待。
(5) 减少性能损失 - 休眠队列
循环等待和线程主动放弃的方案都存在一个共有的问题,即存在太多偶然性。调度由操作系统控制,如果一旦调度不合理,将会出现线程一直循环等待 / 主动放弃的情况。
采用休眠队列是一个更好的方式,具体做法如下:
- 操作系统提供
park()
用于休眠线程,unpark(进程ID)
用于唤醒线程 - 维护一个队列,该队列维护所有正在休眠的线程
- 当线程尝试加锁发现锁已被占用时,将自身线程 ID 存入队列中,休眠自己
- 当线程释放锁时,如果发现队列非空,则唤醒某一个线程
参考
- 操作系统导论