进程同步以及进程同步经典问题

进程同步

引入同步原因:

OS引入进程后,既能提高系统的吞吐量,也能提高系统的资源利用率,但是也使得系统的变得复杂,如果不对多个进程进行妥善管理,那么这些进程就会对资源的无序争夺造成混乱,所以要引入进程同步机制,来使得多个并发执行的进程可以按照一定规则共享资源。

进程同步的基本概念

两种形式的制约关系

  • 间接相互制约关系

    多个程序在并发执行时,由于共享系统资源,比如CPU,I/O设备等,导致这些并发程序之间存在着相互制约关系。另外对于打印机、磁带机这些只有一个的临界资源,必须保证多个进程对他的访问时互斥的。对于系统中这些资源,用户要使用,首先申请,不能任由用户进程随意直接使用

  • 直接相互制约关系

    某些应用程序,为了完成任务而创建了多个进程,这些进程为了完成同一项任务而相互合作。直接相互制约关系就源于它们之间的相互合作。如输入进程A和计算进程B,B会等待A的输入后开始执行

    与时间有关的错误:在多道程序环境下,进程怎么运行并不能由程序自身控制(异步性)。由此会产生对共享变量以及其他资源的不正确访问的次序,从而造成进程每次执行的结果不一致。

临界资源

  • 一些硬件资源比如打印机、磁带机等,都是临界资源,另外除了上述的硬件临界资源还有软件临界资源。并发进程间应采取互斥方式,来实现对此资源的共享。

    临界资源是一次仅允许一个进程使用的共享资源。

    例子:生产者消费者问题

临界区(critical section)

把每个进程中访问临界资源的代码称为临界区

进入区:临界区前面增加的用来检查临界资源是否正在被访问的代码

②临界区

退出区:临界区后面加上的代码,来将临界资源 正被访问的标志 恢复为 未被访问的标志

④剩余区:除了进入区、临界区、退出区之外的其他部分代码

同步机制应遵循的规则

  • 空闲让进

    当没有进程处于临界区的时候,表示临界资源正处于空闲状态,可以允许一个请求进入临界取得进程立即进入临界区

  • 忙则等待

    当已有进程进入临界区的时候,表示临界资源正在被访问,其他要进入的进程必须等待。保证互斥访问。

  • 有限等待

    要求访问临界资源的进程。应该在有限时间内可以进入自己的临界区。

  • 让权等待

    当进程不能进入自己的临界区时,应立即 释放处理机。避免进入“忙等”状态

硬件同步机制

由于利用软件解决互斥进程进入临界区有一定的难度和局限。计算机提供一些硬件指令来解决临界区问题。

关中断

是实现互斥最简单的方法之一。进入锁 测试之前关闭中断,完成锁测试并上锁后才能打开中断。即进程在临界区执行时,OS不相应中断,也就不会调度进程,也就不会进程切换。

缺点:

①滥用关中断权利可能造成严重后果

②若时间过长,会影响系统性能

③不适用于多CPU系统,因为一个CPU上关中断,不能防止进程在其他处理器上执行相同的临界区代码

Test-and-Set指令实现互斥

借助“测试并建立”指令TS实现互斥的方法。一般描述:

boolean TS*(boolean *lock){
    boolean old;
    old = *lock;
    *lock = TRUE;//如果进入时false,等效于关闭了临界资源。
    return old;
}

这条指令执行过程不可分割,是一条原语。当lock=FALSE时,表示资源空闲;当lock=TRUE,表示资源正在被使用。

Swap指令实现进程互斥

称为对称指令,用于交换两个字的内容。

void swap(boolean *a,boolean *b){
      boolean temp;
      temp = *a;
      *a = *b;
      *b = temp;
  }

  // 实现进程互斥伪代码

  boolean lock = FALSE;
  do{
   
  boolean key = TRUE;
  do{
  swap(&lock,key);
      }while(key!=FALSE);
  //临界区操作
  lock = FALSE;
  ...
      
  }while(TRUE);

当临界资源忙碌时,其他进程必须不断进行测试。处于一种“忙等”状态,不符合“让权等待”原则。造成处理机时间的浪费。

信号量机制

1965年,DJ提出的信号两机制是一种很有效的进程同步工具。

整形信号量

一个用于表示资源数目的整形量S,除了初始化之外,只能通过两个标准的院子操作wait(s)和signal(s)来访问。这两个操作被称为P,V操作。

wait(s){
    while(s<=0);//没有遵守“让权等待”,造成“忙等”
    s--;
}
signal(s){
    s++;
}

这是两个原子操作,在执行的时候是不能被中断的。当一个进程在修改信号量时,没有其他进程可以同时修改。

记录型信号量

解决整形信号量的“忙等”;

采用记录型的数据结构,包含两个数据项:表示资源数目的整形变量Value,进程链表指针list:

typedef struct {
    int value;//资源数目的整形变量
    struct process_control_block *list;//进程链表
}semaphore;

wait(s)和signal(s)操作可以描述如下:

wait(semaphore *s){
    S->value--;
    if(s->value<0)//资源分配完毕
        block(s->list);//自我阻塞,放弃处理机
}
signal(semaphore *s){//释放资源
    S->value++;
    if(s->value<=0)//表示仍然有等待该资源的进程被阻塞。
        wakeup(s->list);//唤醒链表中的进程。
}

s->value的初始值是1表示只允许一个进程访问临界资源。

AND型信号量

前两个都是针对并发进程需要共享一个临街资源的情况。

这种是针对一个进程需要两个或者更多的共享资源。

基本思想:将进程在运行时需要的所有资源,一次性分配给进程,等到使用完毕后一起释放。要么把它请求的资源全部分配到进程,要么一个也不分配。

信号量集

对AND型信号量机制扩充所得,每次可以对临界资源进行一个单位的申请和释放,扩充为可一次申请N个单位

信号量的应用

实现进程互斥,和实现前趋关系。

实现进程互斥

为临界资源设置一个互斥信号量mutex,初值为1,将进程访问的临界区设置在wait(mutex)和signal(mutex)操作之间,当需要访问临界区前需要先执行wait(mutex)操作,如果没有进程访问该临界资源,wait操作成功,进程就可以进入临界区。如果临界区有进程,wait操作失败,就会造成进程阻塞。当进程退出临界区后,执行signal(mutex)操作,释放临街资源。

利用信号量实现两个进程互斥:

mutex的取值:0,1,-1;

mutex为1:表示两个进程皆未进入需要互斥的临界区;

mutex为0:表示由一个进程进入临界区运行,另一个必须等待,挂入阻塞队列

mutex为-1:表示一个进程进入临界区运行,另一个进程因阻塞在信号量队列中,当前在临界区的进程退出时可以唤醒它;

单个进程进入临界区的代码:

semaphore mutex=1;
while(1){
    wait(mutex)
    临界区
    signal(mutex);
    剩余区;
}

wait(mutex)和signal(mutex)必须成对出现。

实现前趋关系

有两个并发执行的进程P1,P2;p1中的语句s1,p2中的语句s2,需要实现在s1执行后再执行s2

实现:

设置一个公用信号量s,初值为0;

进程p1中执行:s1;signal(s);

进程p2中执行,wait(s),s2;

执行过程:由于s被初始化为0,如果p2先执行一定会被阻塞,只有在进程p1执行完s1,signal(s)之后s增加1即释放一个进程,p2进程才能成功执行s2;

实现如图前趋图:

QQ截图20200322141135

p1(){s1;signal(a);signal(b);}
p2(){wait(a);s2;signal(c);signal(d);}
p3(){wait(b);s3;signal(e);}
p4(){wait(c);s4;signal(f);}
p5(){wait(d);s5;signal(g);}
p6(){wait(e);wait(f);wait(g);s6;}

main(){
    semaphore a,b,c,d,e,f,g;
    a.value=b.value=c.value=d.value=e.value=f.value=g.value;
    cobegin
        p1();p2();p3();p4();p5();p6();
    coend
}

管程机制

由于要访问临街资源的进程都必须自备wait(s)和signal(s)。这样大量的同步操作分布在各个进程中,会给系统管理带来麻烦。可以使用心得管理机制——管程来解决;

定义

代表共享资源的数据结构以及对共享数据实施操作的一组过程组成的资源管理程序共同构成一个模块称为为管程。管程被请求和释放资源的进程所调用。

QQ截图20200322141148

管程的语法描述:

Monitot monitor_name{ //管程名
    share variable declarations; //共享变量说明
    cond declarations	//条件变量说明
    public:			//能被进程调用的过程
    void P1(......)	//对数据结构操作的过程
    {......}
    void P2(......)
    {......}
    void(......)
    {......}
    ......
    {		//管程主体
        initialization code;	//初始化代码
        ......
    }
}

所有进程访问共享资源只能通过管程简介访问。管程每次只允许一个进程进入管程,执行管程内部的过程,从而实现进程互斥;

管程的特性:

  • 模块化;是一个基本数据单位,可单独编译
  • 抽象数据类型;管程中不仅有数据,还有对数据的操作
  • 信息隐蔽;数据结构和过程的实现外部不可见

管程与进程区别

  • 管程定义的是公用数据结构,而进程定义的是私有数据结构PCB
    • 管程把共享变量上的同步操作集中起来,而临界区却分散在每个进程中
    • 管程是为管理共享资源而建立的,进程主要是为占有系统资源和实现系统并发性而引入的
    • 管程是被要使用共享资源的进程所调用的,管程和调用它的进程不能并发工作,而进程之间能并发工作,并发性是进程的固有特性
    • 管程是os中的一个资源管理模块,供进程调用;而进程有生命周期:创建而产生,撤销而消亡

条件变量

引入:

在管程机制中,当某个进程通过管程请求临界资源未能满足时,管程便调用wait原语使该进程等待,但等待的原因会有多个,通过在P,V操作前引入条件变量来说明作为区别。

含义:

条件变量的操作仅仅是wait和signal操作;条件变量是一种抽象数据类型,每个条件变量保存了一个链表,用于记录因该条件变量而堵塞的所有进程

  • 条件变量的定义格式
    • condition x,y;
  • 对条件变量执行的两种操作
    • wait操作(如x.wait):用来堵塞正在调用管程的进程,并把进程塞入到与条件变量x对应的等待队列,并释放管程,直到x条件变化。此时其他进程可以使用该管程
    • signal操作(如x.signal):用来唤醒与条件变量x对应的等待队列中的一个进程(因为x条件而堵塞)。若没有这样的进程,则继续执行原进程,不产生任何的结果

经典同步问题

生产者-消费者问题

利用记录型信号量解决生产者-消费者问题:

描述了一组生产者和一组消费者共享一个缓存池(有N个缓存池),生产者通过缓存池向消费者提供物品

定义两个同步信号量

  • empty : 代表缓存池中空缓存池的数量,初始为n
  • full : 代表缓存池中满缓存池的数量,初始为0
int in=0,out=0;
item buffer[n];
semaphore mutex=1,empty=n,full=0;
void proceducer(){ //生产者
    do{
        producer an item nextp;
        ...
        wait(empty);
        wait(mutex);
        buffer[in]=nextp;
        in :=(in+1)%n;
        signal(mutex);
        signal(full);
    }while(TRUE);
}
void consumer(){
    do{
        wait(full);
        wait(mutex);
        nextc=buffer[out];
        out=(out+1)%n;
        signal(mutex);
        signal(empty);
        consumer the item in nextc;
        ...
    }while(TRUE);
}
void mail(){
    cobegin //并行开始
    	proceducer();consumer();
    coend
}

用于实现互斥的wait(mutex)和signal(mutex)必须成对出现;

教材P63,利用管程解决此问题;

哲学家进餐

记录型信号量解决

所有信号量都被初始化为1,第i位哲学家的活动可以被描述为:

do{
    wait(chopstick[i]);
    wait(chopstick(i+1)%5);
    ...
    //eat
    ...
    signal(chopstick[i]);
    signal(chopstick[(i+1)%5]);
    ...
    //think
    ...
}while(TRUE);

先拿他左边的筷子,再去拿右边的筷子。五个科学家可能会同时饥饿,同时拿起左边的筷子,这时候会引起死锁。可以规定:最多只允许一个科学家拿起左边的筷子。当哲学家左右筷子都可用的时候允许他拿起筷子...

and信号量机制解决哲学家进餐问题

哲学家先获得两个临界资源后方能进餐。

semaphore chopstick chopstick[5]={1,1,1,1,1};
do{
    ...
    ///think
    ...
    sswait(chopstick[(i+1)%5],chopstick[i]);
    ...
    //eat
    ...
    ssignal(chopstick[(i+1)%5],chopstick[i]);
}while(TRUE)

读者-写者问题

是指保证一个writer进程必须与其他进程互斥地访问对象的同步问题;

纪录型信号量解决

semaphore rmutex=1,vmutex=1;
int readcount=0;//没有读进程读
void reader(){
    do{
        wait(rmutex);
        if(readcount==0)wait(wmutex);
        readcount++;
        signal(rmutex);
        ...
        perform read opration;
        ...
        wait(rmutex);
        readcount--;
        if(readcount==0)signal(wmutex);
        signal(rmutex);
    }while(TRUE);
}
void writer(){
    do{
        wait(wmutex);
        perform writer opration;
        signal(wmutex);
    }while(TRUE);
}
void main(){
    cobegin
    	reader();writer();
    coend
}

教材p66利用信号量机制解决此问题;

posted on 2020-03-27 16:54  passionConstant  阅读(212)  评论(0编辑  收藏  举报