并发进程的正确性
执行过程是不确定性和不可重现的 程序错误可能是间歇性发生的
- 独立进程
- 不和其他进程共享资源或状态
- 确定性 输入状态决定结果
- 可重现 能够重现起始条件
- 调度顺序不重要
- 并发进程
- 在多个进程间有资源共享(都要CPU 内存)
- 不确定性
- 不可重现
进程并发执行的好处
进程需要与计算机中其他进程或设备进行协作
- 共享资源
- 加速
- I/O操作和CPU计算可以并行
- 程序可划分成多个模块放在多个处理机上并行执行
- 模块化
- 将大程序分解成小程序(gcc会调用 cpp cc1 cc2 as ld)
- 使系统易于复用和扩展
原子操作(Atomic Operation)
一次不存在任何中断或失败的操作 要么执行成功 要么没有执行
操作系统需要利用同步机制 即允许并发执行 以便进行资源共享 和提高速度 同时保证一些操作是原子操作
进程的交互关系
- 互斥(Mutual Exclusion)
- 一个进程占用资源 其他进程不能使用
- 死锁(Deadlock)
- 多个进程各占用部分资源 形成循环等待
- 饥饿(Starvation)
- 其他进程可能轮流占用资源 一个进程一直得不到资源
临界区(Critical Section)
进程访问临界资源的一段需要互斥执行的代码
1 | entry section |
- 进入区(entry section)
- 检查 可否 进入临界区的一段代码
- 若可进入 设置相应 正在访问临界区 标志
- 退出区(exit section)
- 清除 正在访问临界区 标志
- 剩余区(remainder section)
- 代码中的其余部分
临界区访问规则
- 空闲则入
- 没有进程在临界区时 任何进程可进入
- 忙则等待
- 有进程在临界区时 其他进程不能进入临界区
- 有限等待
- 等待进入临界区的进程不能无限期等待
- 让权等待(可选)
- 不能进入临界区的进程 应释放CPU
临界区的实现方法
- 禁用中断
- 软件方法(共享变量)
- 更高级的抽象方法(操作系统)
不同临界区实现机制衡量标准
- 性能和并发级别
禁用中断
没有中断 没有上下文切换 因此没有并发
- 硬件将中断处理延迟到中断被启用之后
1 | local_irq_save(unsigned long flags); |
- 进入临界区
- 禁止所有中断 并保存标志
- 退出临界区
- 使能所有中断 并恢复标志
禁用中断缺点
- 禁用中断后 进程无法被停止
- 整个系统为此停下来
- 可能导致其他进程处于饥饿状态
- 临界区可能很长
- 导致无法确定响应中断所需的时间
基于软件同步方法
线程通过共享变量来同步它们的行为
Peterson算法
共享变量
1 | int turn; // 表示该谁进入临界区 |
进程i 进入区代码
1 | flag[i] = true; |
退出区代码
1 | flag[i] = false; |
1 | 当进程 j 没有申请进入临界区 则 flag[j] = false; turn = j; |
Peterson算法
1 | do { |
Dekkers算法
对比 Peterson算法 优点是可以很方便扩展到多个线程
1 | 进程 i 申请进入临界区 flag[i] = true; |
1 | do { |
N线程的软件方法(Eisenberg 和 McGuire)
将线程排成环 若要进入临界区 则将 线程的 flag 标志 填写好 再去看 turn 标志
- 线程Ti要等待从 turn 到 i-1的线程 都退出临界区后访问临界区
- 线程Ti退出时 将turn改成下一个请求线程
基于软件同步方法的缺点
- 复杂
- 需要两个进程间共享变量
- 需要忙等待
- 浪费CPU时间 (需要频繁查询共享变量的状态)
更高级的抽象方法
硬件提供一些同步原语
- 中断禁用
- 原子操作指令(从硬件上保证其原子性)
锁(Lock)
一个抽象的数据结构 基于原子操作指令
- 一个二进制变量(锁定/解锁)
- Lock::Acquire()
- 锁被释放前一直等待 然后得到锁
- Lock::Release()
- 释放锁 唤醒任何等待的进程
原子操作指令
现代CPU体系结构提供的一些特殊原子操作指令
测试和置位指令(TS Test-and-Set)
- 从内存单元中读取值
- 测试该值是否为1(真或假)
- 内存单元值设置为1
1 | boolean TestAndSet(boolean *target) { |
交换指令(Exchange)
交换内存中的两个值
1 | void Exchange (boolean *a, boolean *b) { |
TS指令实现自旋锁(Spinlock)
线程在等待的时候消耗CPU时间
1 | class Lock { |
TS指令实现无忙等待锁
在锁的数据结构中加入等待队列
1 | class Lock { |
原子操作指令锁特征
- 优点
- 适用于单处理机或者共享内存的多处理机中任意数量的进程同步(禁用中断只适用于单处理机 多处理机的情况下 禁止单个处理机的中断 其他处理机仍然能够响应中断)
- 简单且容易证明
- 支持多临界区
- 缺点
- 忙等待锁会消耗处理机时间
- 可能导致饥饿 进程离开临界区时有多个等待进程的情况
- 死锁
- 拥有临界区的低优先级进程 但请求访问临界区的高优先级进程获得处理机并等待临界区
同步方法总结
锁是一种高级的同步抽象方法
- 互斥可以使用锁来实现
- 需要硬件支持
常用三种同步实现方法总结
- 禁用中断(仅限于单处理机)
- 软件方法(共享变量 复杂)
- 原子操作指令(单处理机或多处理机均可)