操作系统-进程同步及PV

进程 同步

进程同步是指多个相关进程在执行次序上进行协调。目的是使系统中多个进程之间能按照一定的规则,共享资源(硬件)和相互合作,从而使程序的执行具有可再现性。

两种制约关系

1.间接相互制约:
两个进程A,B,若A申请打印机,而系统已经将打印机分配给B,则A阻塞,直到B进程使用完毕。

2.直接相互制约:
简单来说A,B两个进程,B需要A的数据,若A不提供,则一直阻塞。

临界资源和临界区

在计算机中许多资源一次只能允许一个进程使用,若多个同时访问,则可能造成系统混乱,这些资源被称为临界资源(打印机,共享变量等)。

在每个进程中访问临界资源的那段代码称为临界区,为了使多个进程能有效地共享临界资源,并使程序的执行具有可再现性,系统必须保证进程互斥地使用临界资源。

同步机制用遵循的规则
1.空闲让进
2.忙则等待:当临界资源正在被访问时没其他要求进入临界区的进程必须等待,以保证对临界资源互斥访问。(整形信号量所满足的规则,记录形信号量不满足)。
3.有限等待
4.让权等待不能进入临界区的进程应立即释放CPU(即不满足条件(临界资源不够用))(整形信号量不满足,记录形信号量满足)。

同步机制最常用的使信号量机制,即使用两个原子操作P(wait) V(signal)信号量类型有所不同,主要介绍两种

1.整形信号量

整型信号量是一种被定义为用来表示资源数的整型量S,其值只能被wait和signal操作。S的初始值被设置为可访问的资源数的数量。当用整型信号量用于互斥的时候,S被初始化为1,表示临界资源不能被多个进程同时访问。
算法如下:
wait(s){
while(s<=0){
DO NOTHING;
}
s–;

signal(s){
s++;
}

注意!!!!

整形信号量的wait擦欧总表示申请一个资源,signal操作表示释放一个资源,但存在问题:只要s<=0,wait操作就会不断测试,因而,整形信号没有遵循“让权等待”的规则,即不管满足不满足临界资源所要访问的条件,都会无条件等待。

2.记录形数据量

记录形信号量中除了一个用于代表资源数目的整形变量value之外,还增加一个进程链表指针list,用于连接所有等待该资源的进程。
typedef strcut{
int value;
struct process control_block *list;
}semaphore;
相应的,wait和signal操作可描述为:
wait(semaphore *s){
s->value–;
if(s->value<0) block(s->list);
}
signal(semaphore *s){
s->value++;
if(s->value<=0) wakeup(s->list);

注意!!!!
记录形信号量的wait操作,当s.value减一后,结果小于0,表示系统中无资源可分配,因此进程调用block原语自我阻塞,其PCB被插入信号量的等待队列list中,所以,记录形信号量遵循了让权等待的规则(举个例子,生产者和消费者,若无产品,则消费者无需等待(买也买不了))。

生产者——消费者问题
问题描述:有两组进程共享一个环形的缓冲池,其中的一组进程称为生产者,另一组进程称为消费者。缓冲池由若干个大小相等的缓冲区组成,每一个缓冲区都可以容纳一个产品。生产者进程不断的将生产的产品放入到缓冲池中,消费者进程则不断的将产品从缓冲池中取出。指针 i 和 j 分别指向第一个空闲的缓冲区和第一个装满的缓冲区。

问题分析 :解决这类问题,关键是要理清楚,谁是临界资源?进程间存在什么交互关系?应该如何设置信号量?如何安排PV操作的顺序。

  1. 临界资源分析:缓冲池中的缓冲区是临界资源(就是图中一小块一小块的东西),它一次只允许一个生产者放入产品或者一个消费者从中取出产品消费。

  2. 交互关系分析:生产者访问缓冲区的时候,消费者不能访问,当然,生产者之间也不能访问同一个缓冲区。反之亦然,故而两者间存在访问缓冲区时,存在互斥的关系,但是生产者必须在消费者之前进行(不然你消费什么),因此也存在同步关系。

  3. 思路分析:这里面只有两种进程,关系上只有同步和互斥,因此只要PV操作并合理安排其位置就可以解决此类问题。

  4. 信号量设置:因为存在互斥的问题,所以先设置互斥信号量 mutex = 1。用于控制两个进程对缓冲区的互斥访问,再设置一个信号量full,用于记录当前缓冲池中已经满的缓冲区数,刚刚开始的时候,生产者还没有生产,因此初始值为0.而信号量empty,用来记录当前缓冲区中空的缓冲区数,初始值为n,于是我们很容易看出来,full + empty = n始终成立。

生产者——消费者伪代码分析如下:

//生产者消费者问题
//设置信号量的初始值
semaphore mutex = 1;
semaphore empty = n,full = 0;
int i,j;//设置指针

//生产者进程
void producer(){
    while(true){
        生产一个数据;
        P(empty);//申请一个空白的区域用来存放生产的数据,此时empty - 1
        //申请完了之后,互斥进入缓冲区,使得其他进程不能访问该缓冲区
        p(mutex);//mutex = mutex - 1 = 0
        将数据放入缓冲区;
        V(mutex);//退出缓冲区,互斥信号量恢复为1(即mutex = mutex+1)
        V(full);//此时,缓冲池中的满的缓冲区 +1 
                //对于消费者来说,这就是释放了资源
    }
}

void consumer(){
    while(true){
        P(full);//申请一块满的缓冲区
        P(mutex);//互斥进入
        将数据从缓冲池中取出来;
        V(mutex);//互斥退出
        V(empty);//释放缓冲区,此时缓冲区状态为空
                 //缓冲池中的空的缓冲区 +1
        消费取出的数据。
    }
}

这里注意一下,消费者进程是先取出来再消费的。我们称用来表示互斥的信号量为互斥信号量,代表可使用的资源量称为资源信号量,那么我们应该先对资源信号量进行P操作后,才能对互斥信号量进行P操作。

那如果我们反其道而行之呢?分析一下。先执行p(mutex)再执行P(empty);。倘若此时,生产者已经将缓冲池放满,消费者并没有来取产品(即empty = 0);下次仍然是生产者运行,它先执行p(mutex),被阻塞,希望消费者取出产品后将其唤醒,但是这个时候,由于先执行的是p(mutex),mutex的值为0,信号量被封锁,消费者进程进不去临界区,因而被阻塞。这样双方都指望对方唤醒自己,然后又都陷入阻塞。因而陷入无休止的等待。这种状态我们称为死锁

读者——写者问题
问题描述:一个数据文件被多个并发进程所共享,其中一些进程只要求读取文件的内容,而一些进程则要求对文件内容进行修改。我们称前者为读者,后者为写者。因为读者并不改变文件的内容,所以我们允许多个进程同时访问,而写者不同,他们要改变数据对象中的内容,

因此一只能允许一个写者进程,并且写的时候也不能有读者进程进行读取。我们把限制条件罗列一下:

  1. 允许任意多个进程同时进行读操作

  2. 一次只能允许一个写进程进行写操作

  3. 若有一个写进程进行写,那么所有进程都不能对文件操作(包括读)

  4. 写者在执行写操作的时候,应该要求已经在对文件进行操作的读,或者写进程退出。

问题分析

临界资源分析:显然被访问的文件是临界资源
交互关系分析:写者与所有的进程都互斥,读者与读者都不互斥
具体思路分析:对于写者而言,它与所有的进程都互斥,用简单的PV操作就可以完成。但是读者的问题较为复杂,它除了要与写者之间实现互斥,还要实现与其他写者的同步(因为写者要在读者离开以后才能写)。那么,设置一个计数器,用来判断当前是否有读者在读文件。有则加1,那么这个变量对于读者来说是个共享的变量,人人都可以访问,所以读者间也要互斥的访问。

信号量的设置:现在很明确,我们要设置信号量Rcount,用来记录当前读者的数量,初始值为0,设置Rmutex为互斥信号量,初始值为1,用于读者之间对计数器的互斥访问,设置Wmutex,用于写者之间的互斥访问,初始值为1。
实现的伪代码如下:

semaphore Wmutex,Rmutex = 1;//互斥信号量
int Rcount = 0;
//读者进程
void reader(){
    while(true){
        P(Rmutex);//读者进程互斥访问计数器变量
        //如果此时没有读者进程,那么先申请Wmutex,使它 =0
        if(Rcount == 0) P(Wmutex);//这样所有的写进程都被阻塞
        Rcount ++;//否则读者进程数 +1,表明现在多了一个读者进程
        V(Rmutex);//释放计数器信号量
        ..............
        ...读取文件...
        ..............
        P(Rmutex);//每次访问Rcount都是以互斥的形式
        Rcount--;//读完文件后,进程退出,计数器减一
        //如果此时没有读者进程,那么唤醒写者进程,允许写
        if(Rcount == 0)V(Wmutex);
        V(Rmutex);//释放互斥信号量

    }
}

//写者进程
void writer(){
    while(true){
        P(Wmutex);
        ............
        ..写入文件..
        ............
        V(Wmutex)
    }
}

上面的代码是一种读者优先的策略。那么哪里体现了优先呢?我们看看读者进程中的这两句代码:

P(Rmutex);//读者进程互斥访问计数器变量
//如果此时没有读者进程,那么先申请Wmutex,使它 =0
if(Rcount == 0) P(Wmutex);//这样所有的写进程都被阻塞
若读者计数器为0,那么这时候可能有这么一种情况,读者写者进程同时要求访问这个文件,但是他们不能同时访问,因为读的时候不能写,写的时候不能读。所以,上面的做法是 P(Wmutex),将写者进程的信号量申请掉,也就是Wmutex = 0,那么写者进程由于再P的话就会进入阻塞状态。相当于让步给读者进程进行操作。带操作完成后再V(Wmutex),唤醒写者进程进行操作。

因此我们换个角度,也可以让写者优先,我就具体的不说了,原理同上。

但是注意:无论是读者优先还是写者优先,当优先级较高的进行操作的时候,那么优先级较低的就必须等待。如果后面来的都是优先级较高的操作,那么致歉优先级较低的就会被无限期的挂起,造成饥饿现象。

于是一种公平策略

例子转载自https://zhuanlan.zhihu.com/p/47619744

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

就是氧气c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值