一、背景
协作进程能与系统内的其他执行进程相互影响。协作进程或能直接共享逻辑地址空间(即代码和数据),或能通过文件或消息来共享数据,然而共享数据的并发访问可能导致数据的不一致
1.数据不一致性
- 多个进程并发或并行执行
- 每个进程可在任何时候被中断
- 仅仅进程的部分代码片段可连续执行
- 共享数据并发/并行访问:
- 数据不一致性:又称不可再现性,同一进程在同一批数据上多次运行的结果不一样
- 解决方法-同步(互斥)机制
- 例:有界缓冲
- n个缓冲区的有界缓冲问题
- 增加变量counter,初始化为0
- 向缓冲区增加一项时,counter加1
- 从缓冲区移去一项时,counter减1
- 对于数据不一致的解决方案:关于的counter的加减操作必须被原子性地执行
2.竞争条件
- 竞争条件:多个进程并发执行访问同一共享数据并且执行结果与特定访问顺序有关的情况
- 防止竞争条件方法:并发进程同步或互斥
3.同步和互斥
二、临界区问题
1.临界资源(Critical resource)
- 一次只允许一个进程使用的资源
- 又称互斥资源、独占资源或共享变量
- 共享资源:一次允许多个进程使用的资源
- 例:
- 许多物理设备都属于临界资源,如输入机、打印机、磁带机等
2.临界区
- 涉及临界资源的代码段
- 注意点:
- 进程在执行该区时可能修改公共变量、更新一个表、写一个文件等
- 当一个进程在临界区内执行时,其他进程不允许在它们的临界区内执行
- 临界区是代码片段
- 临界区是进程内的代码
- 每个进程有一个或多个临界区
- 临界区的设置方法由程序员确定
- 若能保证诸进程互斥进入关联的临界区,可实现对临界资源的互斥访问
- 临界区问题:设计一个协议以便协作进程
- 进入区:在进入临界区前,每个进程应请求许可,实现这一请求代码区段为进入区
- 退出区:临界区之后可以有退出区
- 剩余区:其他代码为剩余区
- 处理操作系统临界区问题的两种常用方法:
- 抢占式内核:允许处于内核模式的进程被抢占;需认真设计,以便确保内核数据结构不会导致竞争条件;响应更快,进程在释放CPU之前不会运行任意长的时间;更适合于实时编程
- 非抢占式内核:不允许处于内核模式的进程被抢占;数据结构基本不会导致竞争条件
临界区使用准则
- 互斥(Mutual Exclusion)
- 假定进程Pi在某个临界区执行,其他进程将被排斥在该临界区外
- 有相同临界资源的临界区都需互斥
- 无相同临界资源的临界区不需互斥
- 有空让进(Progress)
- 临界区内无进程执行,不能无限期地延长下一个要进临界区进程的等地时间
- 有限等待(Bounded Waiting)
- 每个进程进入临界区前的等待时间必须有限
- 不能无限等待
访问临界区
- 访问临界区过程
- 在进入区实现互斥准则
- 在退出区实现有空让进准则
- 每个临界区不能过大,从而实现有限等待准则
四、硬件同步
- 基于软件的解决方案并不保证在现代计算机体系结构上正确工作
1.设计步骤
- 使用简单的硬件指令,并有效使用它们来解决临界区问题
- 对于单处理器:临界区问题可以简单的加以解决;在修改共享变量时只要禁止中断出现,就可以保证保证当前指令流有序进行,且不会被抢占;多用于非抢占式内核
- 对于多处理器:多处理器的中断禁止很耗时,中断禁止不适合多处理器
- 许多现代操作系统提供特殊硬件指令,用于检测和修改字的内容,或者用于原子地交换两个字;可以采用这些特殊指令,相对简单地解决临界区问题
五、互斥锁
- 临界区问题的基于硬件地解决方案不但复杂,而且不能为程序员直接使用
1.设计步骤
- 采用互斥锁保护临界区,从而防止竞争条件
- 一个进程在进入临界区时得到锁,它在退出临界区时释放锁
- 每个互斥锁有一个布尔变量available,它的值表示锁是否可用
- 如果锁是可用的,那么调用acquire()会成功,并且锁不再可用
- 当一个进程试图获取不可用的锁时,它会阻塞,直到锁被释放
- 自旋锁:忙等待,当有一个进程在临界区中,任何其他进程在进入临界区时必须连续循环地调用acquire(),因为进程不停地旋转,以等待锁变得可用
- 缺点:即当多个进程共享同一CPU时,这种连续循环显然是个问题;忙等待浪费CPU周期
- 优点:当进程在等待锁时,没有上下文切换(上下文切换可能需要相当长的时间);当使用锁的时间较短时,自旋锁换还是有用的
- 自旋锁常用于多处理器系统,一个线程可以在一个处理器上‘旋转’,而其他线程在其他处理器上执行临界区
六、信号量
1.信号量概念
- 早期
- 信号量-软件解决方案
- 保证两个或多个代码段不被并发调用
- 在进入关键代码段前,进程必须获取一个信号量,否则不能运行
- 执行完该关键代码段,必须释放信号量
- 信号量有值,为正说明它空闲,为负说明其忙碌
2.信号量类型
- 计数信号量:
- 二值信号量:
- 变化范围仅限于0和1的信号量(整型信号量)
- 整型信号量:
- 信号量S-整型变量
- 提供两个不可分割的[原子操作]访问信号量
- wait(S)(P(S))、signal(S)(V(S))
- 整型信号量的问题:忙等
- 整型信号量:
- 二值信号量=互斥信号量
- 变化范围仅限于0和1的信号量(整型信号量)
3.信号量S的使用
- S必须设置一次且只能一次初值
- S初值不能为负数
- 除了初始化,只能通过执行P、V操作来访问S
- 互斥信号量使用
- 同步信号量使用