同步与互斥
同步-进程之间需要协作
- 程序定义了每个线程中所有的操作的顺序,符合偏序关系
- 线程间的操作顺序有线程调度决定
同步定义
- 并发执行环境下,保持操作系统间偏序关系的行为,称为同步
- 由应用程序来实现,不是操作系统
互斥-进程之间需要共享资源
临界资源
- 系统中某些资源可以提供给多个进程使用,但是在一段时间内,只允许一个进程访问资源
- 当一个进程在访问该资源的时候,其他想访问该资源的进程必须等待,仅当该进程访问玩并释放资源的时候才允许另一进程对该资源进行访问
- 临济资源:子一段时间内置只允许一个进程访问的资源
互斥:
- 临界资源不能同时访问
- 互斥是操作间并发执行的约束
- 单处理机中,存在并发并不能自动满足互斥的条件
交叉执行处理过程中的问题 - 生产者消费者问题
共享变量处理不好会造成数据不一致
两人同时购票,余票还有一百张
tickets = 100;
R1 = tickets,R1 = R1-1,tickets = R1
R2 = tickets,R2 = R2-1,tickets = R2
交叉执行,则购票后,tckets = 99
Producer-Consumer Problem描述
- 多个生产者和多个消费者进程共享一个缓冲区
- 生产者将生产的产品依次取出放入缓冲区中
- 消费者一次从缓冲区中取出产品消费
The Critical-Section Problem临界区域
- 原子操作:操作中的所有动作要么都做,要么都不做,不可被分割
- 原语:一段程序,要么全执行,要么全不执行
- 临界资源
- 临界区:在程序中访问临界资源的那段代码;进程对临界区必须互斥进行访问
- 对临界资源的访问转化成为对临界区的互斥访问
do{
Entry section
cirtical section
Exit section
remainder section
}while(1);
临界区解决方案
- 忙则等待(互斥):当一个进程正处在某临界区内,任何试图进入其临界区的进程都必须进入代码连续循环,陷入忙等状态。连续测试一个变量直到某个值出现为止,称为忙等。
- 有空让进(前进):无程序在临界区执行时,有程序进入应允许;否则会出现饥饿
- 有限等待:但一个程序进入临界区,应限制其他进程进入临界区的次数,以便申请的进程有机会进入临界区
- 有权让进:等待的时候释放CPU的执行权,受惠其他进程
Peterson 算法
Process Pi
do{
flag[i] = true;
turn = j;
while(flag[j] && trun == j );
critical section;
flag[i] = false;
remainder section;
}while(1);
Process Pj
do{
flag[j] = true;
turn = i;
while(flag[i] && trun == i );
critical section;
flag[j] = false;
remainder section;
}while(1);
- 登记加令牌
- 在门口等级,并把令牌交给另一个进程
- 退出时,消除登记信息
- 如果上方都已经登记,拥有令牌的进程会进入
- 双方尚未登记,拥有令牌,自己会进入
能保证进程互斥进入临界区,不会发生“饥饿”现象
硬件同步机制
在落锁和检测所的时候,关闭中断,可以达到互斥和防止饥饿
关中断
- 单处理机中,当操作一个共享的变量时,可以关中断。
- 对于多处理机来说,时间耗费太多,不高效,不能大规模使用
原子操作和锁机制
原语是将多个指令合并成一个指令,在原语的执行过程中也是不响应中断的,使之成为原子操作,
这个期间,等于是屏蔽中断,也就等价于我们讲的第一种硬件方式----- 关中断
do{
acquire lock;
cirtical section;
release lock;
remainder section;
}while(1);
acquire lock :
- 测试当前锁的状态
- 如果处于开锁状态,关锁,并访问
- 如果处于关锁状态,则等待,直到锁被打开,然后关锁并继续执行
release lock :
开锁
TestAndSet 指令
bool TestAndSet(boolean *target){
boolean rv = *target;//取回锁的状态
*target = True; //加锁
return rv;
}
Process:
while(1){
while(TestAndSet(&lock));
//返回lock的值,并将lock置为True
//如果原来已经上锁,自己也上锁并等待
critical section;
lock = False;//开锁
remainder section;
}
Swap 指令
void Swap(boolean *a,boolean *b){
boolean tmep = *a;
*a = *b;
*b = temp;
}
Process:
while(1){
key = True;//自己加锁
while(k == True)
Swap(&lock,&key);
//key 和 lock值互换,lock为true
//如果lock原先为false,key现在是false
critical section;
lock = False;
remainder section;
}
信号量Semaphore
信号量是一个受保护的量,只能对其进行初始化,wait()及signal()操作 (P、V操作)
整型信号量
只有一个表示资源数量的整型量
semaphore s=1;
wait(s){
while(s <= 0);//test
s -- ;//set
}
signal(s){
s++;//unlock
}
P1:
do{
wait(s);
citical section;
signal(s);
remainder section;
}while(1);
P2:
do{
wait(s);
citical section;
signal(s);
remainder section;
}while(1);
wait和signal操作都是原子的,在执行过程中,不可中断
s可表示为可以使用的资源的数量
自旋锁
当一个线程获取锁的时候,如果所已经被其他线程获取,那么该线程将循环的等待,然后不断的判断锁是否能被成功后获取,直到获取到锁才会退出循环。
优点:自旋锁不会发生进程切换,一直处于用户态,不会使线程进入阻塞态,减少了不必要的上下文切换
缺点:循环等待,占用CPU的时间过长
在单处理机中,一个进程等待一个时间,该事件需要其他进程产生,而其他进程无法执行,也就无法产生该事件。
一般用在多处理器系统中。
记录型信号量
一个信号量是一个结构体。
为了解决整形信号量存在忙等问题,因采取让权等待原则。
在信号量中添加链表指针,用于连接上面描述的多个等待访问临界的进程。
typedef struct{
int value;
struct process *L;//PCB队列
}semaphore;
wait(S){
value--;
if(value <0 ){
add this process to waiting queue
block();
}
signal(S){
value++;
if(value <= 0){
remove a process P from the waiting queue
wakeup(P);
}
}
}
信号量及两个操作的物理含义:
- S.value的处置代表系统中某种资源的数目,故信号量又被称为资源信号量
- S.value < 0,其绝对值代表信号量链表队列的长度,等待的进程数
- wait操作意味着申请一个单位的资源
- signal操作,意味着释放一个单位的资源
注:信号量 只能用于初始化和,wait signal 不能用if判断
信号量的应用——互斥
初始信号量设为1,使得资源在同一时间只能有一个进程访问,达到互斥的效果。
信号量的应用——同步
信号量初值设为别的进程都不执行的情况下,本进程最大的执行次数。(可用资源数)
信号量的应用——前驱关系
- 对每一个边设置一个信号量
- 对某个节点,有入边则在需要协调的操作前加一个wait操作
- 有出边,则在需要协调的操作后加一个signal操作,信号量要保证对应
semaphore a,b;
//initial
a,b = 0;
P1:
do{
...S1;
singnal(a)
}while(1);
P2:
do{
...S2;
singnal(b)
}while(1);
P3:
do{
wait(a);
wait(b);
...S3;
}while(1);
进程同步五大经典问题
三要素:
- 信函量设置
- 信号量初值
- 算法描述
生产者消费者问题
一个生产者进程,一个消费者进程,共享一个缓冲区
一个输入进程向缓冲区中输入数据,另一个进程从缓冲区中取出数据,缓冲区只能存放一个数,开始时缓冲区为空。
semaphore empty,full;//分别代表空缓冲区,与满缓冲区的个数
//initially
full = 0;
empty = 1;
//生产者
do{
生产一件产品
wait(empty);
放入一件产品
signal(full);
}while(1);
//消费者
do {
wait(full);
取出一件产品
signal(empty);
消费产品
}whlie(1);
一个生产者,一个消费者进程,共享N个缓冲区
缓冲区编号0-N-1,假定开始的时候缓冲区都为空
- 设下表in跟踪生产者松鼠的过程,初始为0,每送一个数据,in = (int+ 1)%N;
- 设下标out跟踪消费者取数过程,初始为0,每取出一个数据,out = (out+1)%N;
int in,out;
semaphore empty ,full;//空满缓冲区的个数
in=0,out = 0;
empty = N,full = 0;
P:
do{
wait(empty);
add data to buffer(in);
in = (in + 1)%N;
signal(full);
}while(1);
C:
do{
wait(full);
remove data to buffer(out);
out = (out + 1)%N;
signal(empty);
}while(1);
多个生产者和多个消费者共享N个缓冲区
- 生产者进程需要互斥访问共享变量in,需要设定一个互斥信号量。实现多个生产者之间互斥访问共享变量in
- 消费者进程,需要互斥访问共享变量out;
Semaphero mutex1 = 1,mutex2 = 1,empty = N,full = 0;
P:producer process
while(1){
wait(empty);
wait(mutex1);
//add thet item into the buffer
in = (in+1)%N;
signal(mutex1);
signal(full);
}
C:consumer process
while(1){
wait(full);
wait(mutex2);
//remove an item from buffer
out = (out+1)%N;
signal(mutex2);
signal(empty);
//consume tehh removed item
}
为简化P-C模型的实现,将缓冲池视为临界资源,生产者消费者均互斥访问缓冲池
Semaphero mutex = 1,empty = N,full = 0;
producer process:
while(1){
wait(empty);
wait(mutex);
//add thet item into the buffer
buffer[in] = item;
in = (in+1)%N;
signal(mutex);
signal(full);
}
Consumer process:
while(1){
wait(full);
wait(mutex);
//add thet item into the buffer
item = buffer[out];
out = (out+1)%N;
signal(mutex);
signal(empty);
//consume the removed item
}
- 互斥信号量:在同一个进程中成对出现,mutex
- 同步信号量(资源信号量):出现在不同进程中
- 同步在前,互斥在后
为什么同步在前,互斥在后?
若互斥在前,同步在后,考虑一种情况,消费者进程执行wait(mutex),但是full = 0,阻塞,等待生产者进程执行,但是mutex = 0,生产者阻塞,产生死锁!!!
读者写者问题
-
读者:只读,允许多的多个读者同时访问,但必须与写者互斥,在读者访问期间,允许其他读者访问,拒绝其他写者访问,所有读者都完成访问,释放对象
-
写者:可以同时读写,要互斥访问,写操作完成前,拒绝所有其他对象访问
实现: -
写者:简单互斥
-
读者:第一个计入的读者,拒绝写者,不拒绝读者,最后离开的读者,释放资源。
int readercount = 0;//计数器
Semaphero mutex = 1;//计数器时临界资源互斥
Sempaphero wrt = 1;//读者写者互斥
writer process:
while(1){
wait(wrt);
//wirte
signal(wrt);
}
reader process:
while(1){
wait(mutex);
readercount++;
if(readercount == 1) wait(wrt);//是第一个读者,申请资源,隔离写者
signal(mutex);
//reader is performed
wait(mutex)
readercount--;
if(readercount == 0)
signal(wrt);
wait(mutex);
}
该方案存在的问题:
- 该方案读者优先,导致写者饥饿
- 解决:当有写者到来,该写者阻止后续的读者访问该对象(写着者优先)
哲学家就餐问题
中间有一盘饭,桌上五个哲学家,有5根筷子,必须有两支筷子才能就餐。
Semaphero chopstick[5] initialized to 1
对每个哲学家i:
while(1){
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
//eat
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
//think
}
考虑,当每个人都拿起左手边的筷子时,就会死锁
解决方案有很多:
- 最多只能有四个人同时坐下
- 只有两支筷子都能拿的时候才拿筷子
- 奇数先拿右边筷子,再拿左边,偶数相反
- 只有最后一个人先左后右,其他人先拿右边再拿左边
Semaphero chopstick[5] initialized to 1
Semaphero seats = 4;
对每个哲学家i:
while(1){
wait(seats);
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
//eat
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
signal(seats);
//think
}
后面解决策略不再写出代码,自行思考。
瞌睡理发师问题
某理发店有一个接待室和一个理发室,理发室有一把椅子,接待室有N把,没有顾客理发师睡眠,接待室满顾客离开。
int waiting = 0;
const int CHARIS = N;
semaphore customers = 0,barberReady = 0,mutex = 1;
customer:
while(1){
wait(mutex);
if(waitng < CHARIS){
waiting++;
signal(mutex);
signal(customer);
wait(barberReady);
}
else{
siganl(mutex);
leaving;
}
}
barber process:
while(1){
wait(customer);
wait(mutex);
waiting = waiting-1;
signal(mutex);
signal(barberReady);
//理发
}
吸烟者问题
- 假设一个系统中有三个抽烟者进程,每个抽烟者进程不断的卷烟并抽烟。
- 吸烟要三种材料:烟草,火柴,纸
- 抽烟者一个有烟草,一个有纸,一个有火柴
- 一个供应商无限提共材料,只有两种材料被相应的吸烟者取走之后,才能供应新的两种材料
供应商随即供应其中的一种材料
拓展
两产品入库问题
两种产品的数量的差存在制约关系
-N < A产品数量 - B产品的数量 <M
分解:
- A - B <= M-1
- B-A <= N-1
Semaphore mutex = 1,s1 = M-1,sb = N-1;
Process_inputA:
while(1){
Get a product A;
wait(sa);
wait(mutex);
A 入库
signal(mutex);
signal(sb);//A放一个 B可以多放一个
}
Process_inputB
while(1){
Get a product B;
wait(sb);
wait(mutex);
B 入库
signal(mutex);
signal(sa);//B放一个 A可以多放一个
}
管程 Monitors
信号量 wait() signal()操作不当容易引发死锁。比如顺序问题,漏掉操作
解决方法:
提供更高层的方便用户同步机制,系统将其映射到底层信号量及wait signal 操作
- 可以把管程认为一个类,与一般的类的不同是管程有条件变量,用于控制进程之间的同步
- 并发的进程,要互斥访问管程。在同一时刻只能有一个继承访问管程
- 当进程申请资源而为得到满足时,管程调用wait原语使该进程等待,并将他排到等待队列上
- 在某一时刻,管程会调用signal原语唤醒某一进程
条件变量
进程等待的原因有多个,为了区分这些原因,引入条件变量。
管程中对每个条件变量都予以声明
Condition xy,y;
x.wait;
x.signal;
moinitor monitor-name
{
//shared variable declarations
procedure P1(){}
procedure P2(){}
...
procedure Pn(){}
Initialize code () {}
}
管程解决PC问题
monitor pc{
int in,out,count;
item buffer[n];
condition empty ,full;//条件变量
void put(item i){
//因为管程本身就是互斥访问的,因此对于count就不需要再进行互斥了
if(count >= m) empty.wait;
buffer[in] = i;
in = (in+1)%n;
count++;
if(full.queue) full.signal;//由消费者等待将它唤醒
}
void get(item i){
if(count <= 0) full.wait;
i = buffer[out];
out = (out + 1)%n;
count --;
if(empty.queue) empty.signal;
}
void init(){
in = 0;
out = 0;
count = 0;
}
}
//生产者
do{
...
produce an item
pc.put(nextp);
}while(1);
//消费者
do{
...
pc.geti(nextc);
consumer the item
...
}while(1);
管程互斥机制的实现:
- 如果操作系统支持管程,则又操作系统实现管程的互斥
- 如果系统不支持,则管程由用户自己定义,,设置互斥信号量或者锁机制