目录
1. 背景
2. 一些概念
2.1 竞态条件
2.2 原子操作
2.3 产生竞争的示例
2.4 冰箱例子进行类比
2.4.1 使用便签
2.4.2 使用便签(二)- 将便签放在第一位
2.4.4 更复杂的两种便签
2.4.5 临界区
3. 临界区
3.1 临界区的属性
4. 对临界区代码保护的方法1: 禁用硬件中断
5. 方法2: 基于软件的解决方法
5.1 假设的模型
5.2 使用turn来实现
5.3 改进,使用flag[]
5.4 调整顺序后的flag []
5.5 正确的解法
5.5.1 代码
5.5.2 代码2(更为复杂,了解)
5.6 n个进程的算法
5.6.1 Bakery算法
6. 方法3: 更高级的抽象
6.1 基于硬件的解决方法
6.2 假定是一个lock编程抽象
6.3 实现lock
6.4 示例(看起来有很多行代码,但是硬件上实现了,在执行时是一个整体,不允许被打断)
6.5 代码设计,支持n个进程
6.6 可以进一步改进,解决忙等
6.6.1 把等待的进程放在等待队列,进行睡眠
6.7 优点
6.8 缺点
6.9 总结
1. 背景
- 独立的线程:
- 不和其他线程共享资源或状态
- 确定性→输入状态决定结果
- 可重现建→能够重现起始条件,I/0
- 调度顺序不重要
- 合作线程:
- 在多个线程中共享状态
- 不确定性
- 不可重现
不确定性和不可重现意味着bug可能是间歇性发生的
既然有风险,为什么需要合作
- 进程/线程,计算机/设备需要合作
- 优点1: 共享资源
- 一台电脑,多个用户
- 一个银行存款余额,多台ATM机
- 嵌入式系统(机器人控制:手臂和手的协调)
- 优点2:加速
- I/0操作和计算可以重叠
- 多处理器-将程序分成多个部分并行执行
- 优点3:模块化
- 将大程序分解成小程序
- 以编译为例,gcc会调用cpp, ccl, cc2, as, ld
- 使系统易于扩展
原子性问题
我们希望程序具有的特性
- 无论多个线程的指令序列怎样交替执行,程序都必须正常工作
- 多线程程序具有不确定性和不可重现的特点
- 不经过专门设计,调试难度很高
- 不确定性要求并行程序的正确性
- 先思考清楚问题,把程序的行为设计清楚
- 切忌急于着手编写代码,碰到问题再调试
2. 一些概念
2.1 竞态条件
系统缺陷:结果依赖于并发执行或者事件的顺序/时间
- 不确定性
- 不可重现
怎样避免竞态/p>
让指令不被打断
2.2 原子操作
- 原子操作是指一次不存在任何中断或者失败的执行
- 该执行成功结束
- 或者根本没有执行
- 并且不应该发现任何部分执行的状态
- 实际上操作往往不是原子的
- 有些看,上去是原子操作,实际上不是
- 连x++这样的简单语句,实际上是由3条指令构成的
- 有时候甚至连单条机器指令都不是院子的
- Pipeline, super- scalar, out-of-order, page fault
2.3 产生竞争的示例
- Critical section (临界区)
- 临界区是指进程中的一段需要访问共享资源,并且当另一个进程处于相应代码区域时便不会被执行的代码区域
- Mutual exclusion (互斥)
- 当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并且访问任何相同的共享资源
- 不允许临界区有其它的进程
- Dead lock (死锁)
- 两个或以上的进程,在相互等待完成特定任务,而最终没法将自身任务进行下去
- Starvation (饥饿)
- 一个可执行的进程,被调度器持续忽略,以至于虽然处于可执行状态却不被执行
2.4 冰箱例子进行类比
问题:可能出现重复购买面包的情况。
解决重复购买面包的本质
- 最多有一个人去买面包
- 如果需要,有人回去买面包
解决的方法: 例如,在冰箱.上设置-个锁和钥匙( lock&key )
- 去买面包之前锁住冰箱并且拿走钥匙
- 修复了“太多”的问题:要是有人想要果汁怎么办/li>
- 可以改变“锁(lock) ”的含义
- “锁(lock)”包含“等待(waiting) ”
锁、死锁、解锁
- .Lock (锁):在门、抽屉等物体,上加上保护性装置,使得外人无法访问物体内的东西,只能等待解锁后才能访问。
- Unlock (解锁) :打开保护性装置,使得可以访问之前被锁保护的物体类的东西。
- .Deadlock (死锁) : A拿到锁1, B拿到锁2, A想继续拿到锁2后再继续执行,B想继续拿到锁1后再继续执行。导致A和B谁也无法继续执行。
2.4.1 使用便签
2.4.2 使用便签(二)- 将便签放在第一位
2.4.3 使用便签(三)- 区别不同的便签
2.4.4 更复杂的两种便签
2.4.5 临界区
3. 临界区
3.1 临界区的属性
- 互斥:同一时间临界区中最多存在-一个线程
- Progress:如果-个线程想要进入临界区,那么它最终会成功
- 有限等待:如果一个线程处于入口区,那么在i的请求被接受之前其他线程进入临界区的时间是有限制的
- 无忙等待(可选):如果一个进程在等待进入临界区,那么在它可以进入之前会被挂起
4. 对临界区代码保护的方法1: 禁用硬件中断
概述
禁用中断,不允许它进行切换
缺点
5. 方法2: 基于软件的解决方法
5.1 假设的模型
5.2 使用turn来实现
5.3 改进,使用flag[]
5.4 调整顺序后的flag []
5.5 正确的解法
5.5.1 代码
5.5.2 代码2(更为复杂,了解)
5.6 n个进程的算法
循环的等待、执行。
5.6.1 Bakery算法
银行排队,按 执行。
如果收到相同的 ,就按照进程 来执行。
6. 方法3: 更高级的抽象
6.1 基于硬件的解决方法
- 硬件提供了一些原语
- 像中断禁用,原子操作指令等
- 大多数现代体系结构都这样
- 操作系统提供更高级的编程抽象来简化并行编程
- 例如:锁,信 量
- 从硬件原语中构建
6.2 假定是一个lock编程抽象
6.3 实现lock
- 大多数现代体系结构都提供特殊的原子操作指令
- 通过特殊的内存访问电路
- 针对单处理器和多处理器
- Test- and -Set测试和置位
- 从内存中读取值
- 测试该值是否为1 (然后返回真或假)
- 内存值设置为1
- 交换.
- 交换内存中的两个值
6.4 示例(看起来有很多行代码,但是硬件上实现了,在执行时是一个整体,不允许被打断)
6.5 代码设计,支持n个进程
存在忙等的情况。
6.6 可以进一步改进,解决忙等
6.6.1 把等待的进程放在等待队列,进行睡眠
6.7 优点
- 优点
- 适用于单处理器或者共享主存的多处理器中任意数量的进程
- 简单并且容易证明
- 可以用于支持多临界区
6.8 缺点
- 缺点
- 忙等待消耗处理器时间
- 当进程离开临界区并且多个进程在等待的时候可能导致饥饿
- 死锁
- 如果一个低优先级的进程拥有临界区并且一个高优先级进程也需求,那么高优先级迸程会获得处理器并等待临界区
6.9 总结
- 锁是更高等级的编程抽象
- 互斥可以使用锁来实现
- 通常需要一定等级的硬件支持
- 常用的三种实现方法
- 禁用中断(仅限于单处理器)
- 软件方法(复杂)
- 原子操作指令(单处理器或多处理器均可)
- 可选的实现内容:
- 有忙等待
- 无忙等待
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!