操作系统清华向勇陈渝版笔记(九) 同步协同多道程序设计和并发问题,同步互斥,死锁,临界区

来源:互联网 发布:无情刀永不知错 编辑:程序博客网 时间:2024/05/22 07:03

前篇在此:

操作系统清华向勇陈渝版笔记(七) 进程与线程 PCB TCB 进程挂起 用户线程 内核线程 轻量级进程 僵尸队列

操作系统(八)CPU调度 短剩余时间 吞吐量 轮循 实时调度 多处理器调度 (清华 向勇 陈渝版)

index
9-1 同步互斥、临界区、死锁、互斥概念等等
9-2 临界区和三种满足性能的方法

正文

9-1 同步互斥、临界区、死锁、互斥概念等等

多个进程会交互,对共享资源的访问。处理不当就会饥饿,死锁。

独立的线程:不和其他线程共享资源或状态,不交互,所以具有确定性(输入状态决定结果),可重现(能重现起始条件),I/O,调度顺序不重要
合作的线程:在多个线程中共享状态,不确定性,不可重现
不确定性和不可重现意味着BUG可能是间歇性发生的

为什么要合作?
共享资源(嵌入式系统);
加速,效率高(I/O操作和计算可以重叠,拆分小任务,流水,并行,多处理器);
模块化:大程序分解成小程序,使系统易于扩展

存在的问题——>举例:
这里写图片描述
这里写图片描述

希望是
无论多个线程的指令序列怎么交替,程序都必须正常工作,调试难度很高;
不确定性要求并行程序的正确性,一定更要预先思考。
Race condition 竞态条件
结果依赖于并发执行的时间点 顺序/时间

怎么避免?如何不让指令被打断?
atomic operation原子操作
指一次不存在任何中断或者失败的执行,要么成功done要么没执行
实际操作往往不是原子的,甚至单个机器指令都不一定是原子的。
对内存的load store是原子的,但++ 、–都不是

临界区critical section:
进程中访问共享资源的代码区域,且当另一个进程处于相应代码区域时便不会执行。

互斥mutual exclusion:
任一时刻只能有一个进程进入临界区访问。当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区,并访问任何相同的共享资源。

死锁Dead lock
多个进程相互等待完成特定任务,而最终没法继续自身任务

饥饿starvation:
一个可执行的进程长期等待执行,被调度器持续忽略。

可以留便签,但还是做不到,因为上下文切换不知道何时会发生。
便签上打不同的标签→不同标签的Note+不同代码的线程/进程
一个while 一个if
busy-waiting 当A等待DO nothing时浪费了CPU的时间,忙等待。
而且有不对称性

以买面包bread为例:

假设有一些锁breadlock.acquire(): -----enter the critical section 在锁被释放前一直等待,然后获得锁if(no bread){buy bread;}lock.release():-----exit the critical section 解锁并唤醒任何等待中的锁release and acquire都要是原子操作,如果两个进程同时发现了一个被释放了的锁,那么只有一个能获得

9-2 临界区和三种满足性能的方法

临界区的特点:

  • 互斥
  • progress前进,如果一个线程想要进入临界区,不会一直死等,总能成功
  • 优先等待,如果一个线程i处于入口区,那么i的请求被接受之前,其他线程进入临界区的时间是有限制的。否则,饥饿。是对progress的补充
  • 无忙等待:尽量不要忙等,如果进程一直等待进入临界区,那么在它可以进入之前会被挂起。(可以不满足)

三种方法:禁用硬件中断,基于软件,更高级的抽象

(1)禁用硬件中断:
没有中断,就没有上下文切换,没有并发。减少不确定性。进入临界区禁用中断,退出时再开启。
问题:
一旦禁用中断,线程无法停止
整个系统都会停下来,I/O啥的都没用了,其他线程可能会饥饿 影响效率
临界区要是太长咋整?无法限制响应中断所需的时间,可能有硬件影响。一般都用于短的临界区时间。
如果两个CPU并行的话,一个CPU只能屏蔽自身,另一个仍可能产生中断。

(2)基于软件的方式

这里写图片描述

共享变量初始化

int turn=0;         //初始是Ti进入临界区对Thread Ti,有:do{    while(turn!=i);     //  busy waiting    critical section    turn=j;                 //after exit turn=j    reminder section}while (1);

Ti在退出临界区后做其他的事情去了,Tj想继续运行,但必须等待Ti处理临界区

必须是一种交替循环。
改进,用数组flag

flag[i]==1 进程i想进入临界区执行int flag[2];flag[0]=flag[1]=0;do{while(flag[j]==1) ;  //初始flag都是0,没有满足互斥flag[i]==1;   //先请求critical sectionflag[i]=0;remainder section}while (1)如果先flag[i]=1while循环,可能死锁

满足进程Pi and Pj之间互斥的正解:

use two shared data itemsint  turn; // who ‘s turn to enter the critical sectionbollean flag[];  //whether the process is ready to enterPETERSON算法do{flag[i] = TURE;turn=j;while(flag[j]&&turn==j ) ; CRITICAL SECTIONflag[i]=FLASE;    REMAINDER SECTION} while(TURE);

对N个进程,Eisenberg and Mcguire’s algorirhm

这里写图片描述
进程i之前的先进入了i再进入,i之后的等i先进入

Bakery算法
N个进程的临界区 排号,只有一个窗口
进入前,进程接受一个数字,数字最小的进入临界区。如果Pi和Pj
得到相同的数字,比较i,j的大小,小的进入。编号方案总是按照枚举的增加顺序生成数字。

问题:
复杂;需要共享数据项
耗资源;需要忙等待,浪费CPU时间
没有硬件保证的情况下无真正的软件解决方法;load store必须要是原子操作

(3) 基于硬件原子操作的高层抽象实现
硬件提供了一些原语,用原子操作直接实现进退临界区
锁是一个抽象的数据结构,获得锁就是进入临界区的实现过程
lock_next_pid->acquire();
new_pid=next_pid++ ;
lock_next_pid->release();

大多数现代体系结构都提供特殊的原子操作指令
(1) test-and-set 测试和置位
从内存中读值,判断是否为1并返回,同时设置内存值为1
(2) exchange 交换 输入两个内存单元,交换其值并返回
这两条如果有一条可以是原子指令,就可以完成

这里写图片描述

lock::release(){value=0;}
可以支持N个进程的操作且是一样的,都很简洁
改进:让它不忙等
当它等待其他事件时,可以睡眠/阻塞

class lock{ int value = 0; waitqueue q;}lock::acquire(){while (test-and-set(value)){add this TCB to wait queen q;schedule();}}lock::release(){value=0;remove one thread t from q;wakeup(t);}

如果临界区短,开销小于上下文切换的开销,直接忙等,否则要引入WAITING和wakeup

int lock=0;to process Ti that want to enter the critical sectionint key;do{key=1;while(key==1)exchange(lock,key);   //when key!=1 swap out ofcritical section  //at that time key=0 lock=1    lock=0;remainder section}

实现简单,易扩展到多临界区,开销小,适用于单处理器或共享主存的多处理器中任意数量的进程 广泛使用
缺点: 还是有忙等,浪费时间;
抢LOCK随机可能某个一直抢不到,当进程离开临界区,且多个进程在等待时可能导致饥饿;
也许死锁,一个低优先级的进程拥有临界区,一个高优先级的 进程也需求,那么高优先级进程忙等,占用cpu,低优先级的不能释放Lock,要通过优先级反转来解决。

总结:

用锁来解决互斥问题,锁是高层编程抽象,需要一定硬件支持
常用三种:禁用中断(仅可单处理器),软件方法(复杂),原子操作指令(单处理器或多处理器都可以)
可选的实现内容:有忙等待,无忙等待(进程睡眠)

这里写图片描述

下一篇在此:操作系统清华大学版笔记(十) 信号量、管程、条件互斥、经典同步问题(读者写者、哲学家问题)

阅读全文
0 0