问题描述

有读者和写者两组并发线程,共享一个数据库,当两个或以上的读线程同时访问共享数据时不会产生副作用,但若某个写线程和其他线程(读线程或写线程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:

允许多个读者可以同时对文件执行读操作;
只允许一个写者往文件中写信息;
任一写者在完成写操作之前不允许其他读者或写者工作;
写者执行写操作前,应让已有的读者和写者全部退出。
也就是说,读进程不排斥其他读进程,而写进程需要排斥其他所有进程,包括读进程和写进程。

问题分析

关系分析:由题目分析可知,读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。
整理思路:写者是比较简单的,它与任何线程互斥,用互斥信号量的 PV 操作即可解决。读者的问题比较复杂,它必须实现与写者的互斥,多个读者还可以同时读。解决方案有两种:读者优先与写者优先。
读者优先:只要有一个读者处于活动状态(正在读),后来的读者即使比写者后到也会被接纳。如果读者源源不断地出现的话,那么写着就始终处于阻塞状态,直到所有读者读完为止。
写者优先:一旦写者就绪,那么写着会尽可能快地执行写操作。如果写者源源不断地出现的话,那么读者就始终处于阻塞状态。

使用信号量实现
读者优先

读进程只要看到有其他读进程正在访问文件,就可以继续作读访问;写进程必须等待所有读进程都不访问时才能写文件,即使写进程可能比一些读进程更早提出申请。可以使用一个计数器rcount记录读者总数目(包含等待和正在读的数目),如果rcount大于0则写者等待,而读者直接读。当rcount==0写者与写者、写者与第一个读者抢占读写操作,这可以用一个二元信号量wt进行互斥访问。因为多个读者线程都要访问计数器,则使用一个二元信号量mutex进行互斥访问。

void writer
{
    while(true)
    {
        P(wt);
        write();
        V(wt);
     }
 }
 void reader()
 {
     while(true)
     {
         P(mutex)           //对rcount进行互斥访问
         rcount++;  
         if(rcount==1)      
             P(wt)          //如果是第一个读者,与写者互斥抢占数据库
         V(mutex)

         read()

         P(mutex)          //对rcount进行互斥访问
         rcout--;
         if(rcount==0)
             V(wt)         //如果是最后一个读者,释放数据库所有权
         V(mutex)
     }
}

对于读者优先,在读访问非常频繁的场合,有可能造成写进程一直无法访问文件而饿死的局面。

写者优先

使用一个计数器wcount记录当前写者的总数据(包含等待和正在写的线程的数据)。当wcount>0,使用信号量read禁止所有的读进程。为了对wcount进行互斥访问,需要使用一个二元信号量wtmutex。
如果当前有读者正在写,写者需要阻塞,所以读者需要定义一个计数器rcount来记录当前正在读的线程数目,当rcount=0时写者才可以写数据。rdmutex为了对rcount互斥访问。
对于读进程,处理read信号量,还需要一个额外的信号量x,这样防止在read信号量等待着大量的读进程。

void reader()
{
    while(true)
    {
        P(x)                 //用来保证阻塞在read信号中排队的读者至多只有一个。其余的阻塞在x上
        P(read)              //等待是否有写进程
        P(rdmutex)           //对rcount进行互斥访问
        if(rcount ==0)       //如果是第一个读操作,与写者互斥抢占数据库
           P(wt)
        rcoutn++;
        V(rdmutex)
        V(read)
        V(x)
          read()  //读操作
        P(rdmutex)           //对rcount进行互斥访问
        rcount--;
        if(rcount==0)        //如果是最后一个读者,释放数据库所有权
            V(wt)
        V(rdmutex)
    }
}
void writer()
{
    while(true)
    {
        P(wtmutex)
        if(0 == wcount)
            P(read)             //与写操作
        wcount++;
        V(wtmutex)
        P(wt)
          write()  //写
        V(wt)
        P(wtmutex)
        wcount--;
        if(wcount==0)
            V(read)
        V(wtmutex)
    }
}

reader函数使用信号量x的必要性:假如没有使用信号量x,考虑开始只有写线程,那么后来的读线程就会全部阻塞在P(read)中。当wcount==0,读线程开始转为就绪开始执行,当这时又有写线程,那么写线程就会阻塞在P(read)中,直到前面所有阻塞在read信号量的读线程都执行完。引入信号量x的目的就是:只让一个读线程阻塞在read中,其余所有的线程阻塞在x中,这样即使前面等待了大量的读线程,但由于只有一个线程阻塞在read,那么写线程也会立即执行。

———————
版权声明:本文为CSDN博主「sunny_ss12」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sunny_ss12/article/details/47435687

Last modification:March 9th, 2020 at 05:53 pm
如果觉得我的文章对你有用,请随意赞赏