您的位置:首页 > 其它

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

2017-12-21 14:59 876 查看
前篇在此:

操作系统清华向勇陈渝版笔记(七) 进程与线程 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 section
flag[i]=0;
remainder section
}while (1)

如果先flag[i]=1再while循环,可能死锁


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

use two shared data items
int  turn; // who ‘s turn to enter the critical section
bollean flag[];  //whether the process is ready to enter
PETERSON算法
do{
flag[i] = TURE;
turn=j;
while(flag[j]&&turn==j ) ;
CRITICAL SECTION
flag[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 section
int key;
do{
key=1;
while(key==1)exchange(lock,key);   //when key!=1 swap out of
critical section  //at that time key=0 lock=1
lock=0;
remainder section
}


实现简单,易扩展到多临界区,开销小,适用于单处理器或共享主存的多处理器中任意数量的进程 广泛使用

缺点: 还是有忙等,浪费时间;

抢LOCK随机可能某个一直抢不到,当进程离开临界区,且多个进程在等待时可能导致饥饿;

也许死锁,一个低优先级的进程拥有临界区,一个高优先级的 进程也需求,那么高优先级进程忙等,占用cpu,低优先级的不能释放Lock,要通过优先级反转来解决。

总结:

用锁来解决互斥问题,锁是高层编程抽象,需要一定硬件支持

常用三种:禁用中断(仅可单处理器),软件方法(复杂),原子操作指令(单处理器或多处理器都可以)

可选的实现内容:有忙等待,无忙等待(进程睡眠)



下一篇在此:操作系统清华大学版笔记(十) 信号量、管程、条件互斥、经典同步问题(读者写者、哲学家问题)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐