背景
前置学习
-
多道程序设计
-
并发
-
进程、线程
-
内存管理
-
进程调度
独立的线程
-
不和其他线程共享资源或状态
-
确定性:输入状态决定结果
-
可重现
-
调度顺序不重要
合作线程
-
在多个线程中共享状态
-
不确定性
-
不可重现
合作优点
-
共享资源
-
加速
-
I/O操作和计算可以重叠
-
多处理器
-
-
模块化
合作缺点
-
多线程程序具有不确定性和不可重现的特点
-
不经过专门设计,调试难度很高
概念
前面出现多个线程/进程共享资源成为Race condition 竞态条件
避免竟态----原子操作
原子操作:要么成功返回,要么不执行,不存在执行中的状态
临界区:指的是一个访问共用资源的程序片段,而这些共用资源又无法同时被多个线程访问的特性
互斥:指当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并且访问任何相同的共享资源
死锁:是指两个或以上进程,在相互等待完成特定任务,而最终没法将自身任务进行下去
饥饿:是指一个可执行的进程,被调度器持续忽略,以至于虽然处于可执行状态却不被执行
临界区
互斥: 同一时间临界区中最多存在一个线程
前进(progress):如果一个线程想要进入临界区,那么它最终会成功
有限等待:如果一个线程i处于入口区,那么在i的请求被接受之前,其他线程进入临界区的时间是有限制的
无忙等待(可选): 如果一个进程在等待进入临界区,那么在它可以进入之前会被挂起
访问临界区资源
禁用硬件中断
没有中断,没有上下文切换,因此没有并发
-
硬件将中断处理延迟到中断被启用之后
-
大多数现代计算机体系结构都提供指令来完成
进入临界区:禁用中断
离开临界区:开启中断
缺点
:
一旦中断被禁用,线程就无法被停止
-
整个系统都会为该线程而停下来
-
可能导致其他线程处于饥饿状态
若临界区可以任意长,无法限制响应中断所需的时间
基于软件的解决方案
Dekker算法(1965): 第一个针对双线程例子的正确解决方案
Bakery算法(1979): 针对n线程的临界区问题解决方案
更高级的抽象
硬件提供了一些原语
操作系统提供更高级的编程抽象来简化并行编程
-
例如,锁,信号量
-
从硬件原语中构建
锁是一个抽象的数据结构
-
一个二进制状态(锁定,解锁),两种方法
-
Lock::Acquire() 锁被释放前一直等待,然后得到锁
-
Lock::Release() 锁释放,唤醒任何等待的进程
大多数现代体系结构都提供特殊的原子操作指令
-
通过特殊的内存访问电路
-
针对单处理器和多处理器