进程的同步与互斥

来源:互联网 发布:js权威指南第七版pdf 编辑:程序博客网 时间:2024/05/22 02:13

操作系统提供的一大功能就是进程管理。而多个进程在访问同一资源时,不可避免的会出现竞争的现象,为了解决这一问题,引入了信号量以及P、V操作。
信号量可以看作操作系统中某一资源,当信号量大于0时表示该资源的剩余数量,当信号量小于0时表示有多少个进程在等待队列中。仅P、V操作可以实现对信号量的修改。
P操作表示有一个进程申请使用资源,因此S–,如果s<0,则将该进程加入到信号量的等待队列中,否则继续运行(一般下面的代码都是操作该资源的代码,我们称为临界区)
V操作表示有一个进程释放了某一资源,S++,如果s<=0,则表示之前有至少一个进程处于等待队列中,需要从等待队列中取出一个进程执行。
上述对信号量以及P、V操作的描述需要好好理解,因为大部分进程间同步和互斥的问题都可以用上述操作来进行管理。
我们先来看看几个经典的同步互斥模型。

模型一、生产者消费者问题
生产者消费者描述的是这样一个问题,两个进程,其中一个进程(生产者)用于产生某种资源(可以是数据),放置到一个容器中(我们先假设仅能放一个),另一个进程(消费者)使用该资源。
那么问题来了:当生产者还未产生资源的时候,消费者进程应该等待,当生产者产生了这个资源后去通知消费者;同样的,如果生产者已经生产了一个资源时,消费者还未消费掉,那么生产者应该等待,当消费者消费后再去通知生产者继续生产。
典型的生产者消费者问题的解决方法很简单,定义两个信号量(想一想为什么是两个)。其中一个empty表示容器是否为空(这个也可以理解为一种资源不是吗),另一个full表示容器是否满。生产者通过第一个信号决定是等待还是放置数据,消费者通过第二个信号两决定是等待还是消费数据,下面是代码描述:

int full = 0;int empty = 1;void producter() {    product_sth(); //生产一个数据    P(empty);     //判断是否等待还是放置数据    //临界区代码,放置数据    do sth.     V(full);     //通知full的等待进程,即消费者}void comsumer() {    P(full);   //判断等待还是取出数据    //临界区代码,取出数据    do sth.    V(empty) //通知生产者继续生产数据    comsume_data(); //消费数据}

代码很简单,大家可以根据注释理解一下,关键在于两个信号量以及相互之间的等待和通知。

模型二、读者-写者问题
上面的生产者消费者模型可以看作是进程间协作的典型,下面我们看一下进程间互斥的模型,最简单的互斥模型其实很简单,访问临界区前先进行P操作,访问临界区后进行V操作:

void func() {    P(S);    //临界区代码    do sth.    V(S);}

多个进程间如果是竞争关系,那么上述代码足以应付,怎么样是不是很简单。
那么接下来看看读者写者问题又是怎么回事。
假设有诺干读者和写者需要对同一块磁盘区域进行读写,那么规定如下:
1.读者之间可以同时读取
2.写者和读者不能同时进行
3.写者和写者之间也不能同时进行

我们来想想这个问题和上面典型的互斥模型有什么区别:首先读者和写者,写者和写者间不能同时进行,证明肯定是有个信号量进行限制,读者先使用的话写者需要等待,写者使用的话读者需要等待。嗯,到这一步之前都还和典型互斥模型一样。然后,读者和读者之间可以同时进行。这个时候就有问题了,如果还是这个信号量的话,一旦有读者进行读取,那下一个读者直至第一个读者读完都还不能读取,这个不满足第一个条件。那么应该再用一个信号量吗?好像不对,不管怎么用信号量都不能完美表示多个读者同时使用一个资源的情况。
我们换一个思路:读者其实1个和100个是没有区别的,唯一有区别的是1个读者和0个读者的区别,那么只要给读者数量来个计数器,读者访问磁盘资源时,查看这个计数器,只有当这个计数器由0变为1时才进行P操作。同样的,当读者读完磁盘进行释放时,也先查看这个计数器,只有当这个计数器由1变为0时才进行V操作。这样这个问题就完美解决了,我们看看示例代码:

int ct_reader = 0; //读者计数器int ct_mutex = 1; //修改读者计数器的信号量,因为是在多个进程中对ct进行修改int write = 1; //读写磁盘的信号量void reader() {    P(ct_mutex);  //操作ct,先加锁    ct_reader++;    if(ct_reader == 1) P(write);  //仅当第一个读者加锁    V(ct_mutex);    //reading...    p(ct_mutex);    ct_reader--;    if(ct_reader == 0) V(write);  //仅当读者由0变为1时解锁    V(ct_mutex);}void writer() {    P(write);    //writing...    V(write);}

上面已经分析过了,大家可以参照注释理解一下,关键思想其实就是读者是一个特殊的进程,对临界资源加锁是在特定的条件下进行的。

怎么样,是不是觉得进程间的同步互斥也不是很难,无非是信号量和P、V操作嘛。那么下面留个课后题,上述读者写者模型,如果增加一个条件,当有写者到来的时候,仅当前读者读完后立即开始写操作,即写者优先,这个时候又应该如何设计?

0 0
原创粉丝点击