读者写者问题

文章介绍了互斥和同步的概念,通过举例说明线程对临界区的访问问题。通过信号量机制,演示了如何实现互斥,防止多个线程同时访问共享资源。还详细讨论了读者写者问题,并提供了两种解决方案:读者优先和写者优先,以及公平策略的实现,以协调读者和写者的并发访问。
摘要由CSDN通过智能技术生成

1.问题描述

  1. 读-读:允许多个读者同时读
  2. 读-写:互斥,同一时刻只允许读者读或者写者写
  3. 写-写:互斥,同一时刻只能有一个写者写

2.问题解决

2.1前置概念

2.1.1什么是互斥,什么是同步
  • 互斥:线程A和线程B不能在同一个时刻临界区进行操作。比如线程A是A某人,B线程B某人,碗就是临界区,饭就是临界区的数据,吃饭就是对临界区的数据进行操作,A某人不可能和B某人在同一个时刻吃碗里的饭。要是它们在同一个时刻吃了碗里的饭,A某人吃了一口,然后发现碗里少了两口饭,百思不得其解…。
  • 同步:线程A必须在线程B之前临界区进行操作。比如线程A的操作:用碗盛饭,线程B的操作:用碗吃饭。要是线程B在线程A前面执行,你吃什么。
2.1.2什么是临界区
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
#define SUM 10000

int n = 2 * SUM; //共享变量

//线程函数,对n进行sum次n=n+1的操作
void* test()
{
    for (int i = 0; i < SUM; i ++)
    {
        n = n + 1;
    }
}

int main(void)
{
    while(n == SUM * 2)
{
    n = 0;
    pthread_t t1, t2;
    pthread_create(&t1, NULL, test, NULL);//创建一个线程执行test()函数
    pthread_create(&t2, NULL, test, NULL);//创建一个线程执行test()函数
    //等待线程完成
    pthread_join(t1, NULL);
    //sleep(10);
    pthread_join(t2, NULL);
    printf("共享变量n的值为%d\n", n);
}
    return 0;
}

下面一段代码中,定义了一个test()函数,对共享变量做SUM次+1的操作,mian()函数中创建了两个线程t1, t2,分别执行test()函数,理论上讲n的值永远是2SUM,这个程序根本不会终止,但结果是:
在这里插入图片描述
没错!它终止了,n的值为SUM,其实n的值应该在SUM到2
SUM之间中,任意一个值都是可以的。
分析下原因:
当CPU执行n = n + 1时,需要拆成三步:从内存中取出n值放到寄存器中,对寄存器中的n值进行+1操作,把寄存器中完成+1操作后的n值放回到内存中。 假如:内存中n的值为100,现在线程t1,从内存中取出n=100放入寄存器中,然后进行+1操作,此时寄存器中n=101,但内存中的值还是100,在线程t1把寄存器中n=101的值放回到内存中之前, 很不巧的发生了时钟中断,CPU停止线程t1的执行,CPU切换执行线程t2:取出内存的值n=100放到寄存器中,对寄存器中n进行+1操作n=101,然后把n=101放回到内存中,然后CPU去执行线程t1还没有执行完的操作,把寄存器中n=101值再次放入到内存中。这时就n少了一次+1的操作。代码中{n=n+1}就是临界区,对应碗,n就是数据或者资源,对应饭,所以就需要互斥操作解决这个问题,不允许两个线程或者多个线程同时对临界区进行操作

2.1.3 怎么用信号量来实现互斥

信号量:通常表示资源的数量,是一个整型变量sem,一般有两个原子操作:P操作对sem进行-1操作,V操作对sem进行+1操作。
信号量PV操作实现互斥
初始化信号量sem=1,表示同一个时刻,只允许一个线程进入临界区。线程在进入临界区前,进行P操作, sem -=1,如果sem < 0, 说明之前已经有一个线程进入临界区(因为之前有一个线程进入临界区但还没有离开,所以此时sem=0,进行P操作后sem=-1 < 0),线程会进入阻塞状态,直到被其它线程唤醒。线程在离开临界区之后,进行V操作,sem+=1,如果sem<=0,说明有线程被阻塞了(我这个线程在对临界区进行操作时,前面有另一个线程也要进入临界区,发现临界区中已经有了,只能被阻塞,此时sem=-1,然后我这个线程离开临界区进行V操作,sem = (-1 + 1 = 0 ) <= 0,我这个线程就知道了之前有线程被阻塞,然后我就需要区唤醒另一个线程,让它从阻塞态变为就绪态),需要去唤醒之前被阻塞的线程。
举个例子

  1. 时刻T1:初始化信号量sem = 1。
  2. 时刻T2:线程A进入临界区,sem = 1 - 1 = 0 (P操作),不满足sem < 0的条件。
  3. 时刻T3: 线程B进入临界区,sem = 0 - 1 = -1 (P操作),满足sem < 0的条件,所以线程B被阻塞。
  4. 时刻T4:线程离开临界区,sem = -1 + 1 = 0(V操作),满足sem <= 0的条件,所以线程A要去唤醒线程B。然后B进行P操作后,进入临界区,然后离开临界区后进行V操作。

可以初始化信号量的值为n,允许同一个时刻n个线程进入临界区。

信号量PV操作实现同步
初始化信号量为0,**举个例子:**我们需要线程A在线程B之前执行对临界区的操作。只需线程A中执行V操作,线程B中执行P操作。

  1. 时刻T1: 初始化信号量为sem =0
  2. 时刻T2:线程B执行P操作,想要操作临界区,sem = 0 - 1 = -1 < 0,被阻塞
  3. 时刻T3:线程A操作完临界区后,执行V操作,sem = -1 + 1 = 0 < = 0,线程A去唤醒线程B。

读者写者问题解决方案

解决方案
#include <stdio.h> 
#include <pthread.h> 
#include <semaphore.h> 
#include <unistd.h> 
#include <stdlib.h>

sem_t wMutex; //用于控制写操作的互斥信号量(为了只让一个写者写,不允许两个写者线程写数据)
sem_t rCountMutex; //用于控制对Rcount(读者修改)的修改为互斥操作,(不容许两个线程同时对Rcount变量操作)
int rCount = 0; //记录读者的数量

void* writer()
{
    while(1)
    {
        sem_wait(&wMutex); //进入【写者写入数据的临界区】,不允许其它写者写数据
        printf("写者正在写入数据......");
        sem_post(&wMutex); //离开【写者写入数据的临界区】,允许其它写者写数据
    }
}

void* reader()
{
    while(1)
    {
        sem_wait(&rCountMutex); //进入【rCount】临界区
        if(rCount == 0)
        {
            sem_wait(&wMutex); //不允许其它写者写数据,因为读者正在读取数据,必须阻塞写者
        }
        rCount++; //读者人数+1
        sem_post(&rCountMutex); //离开【rCount】临界区

        printf("当前有%d位读者正在读取数据", rCount);


        sem_wait(&rCountMutex); //进入【rCount】临界区
        rCount--; //读者人数-1
        if(rCount == 0)
        {
            sem_post(&wMutex); //最后一个读者离开临界区,唤醒写者写入数据
        }
        sem_post(&rCountMutex); //离开【rCount】临界区

    }
} 
int main() 
{ 
    sem_init(&wMutex, 0, 1); // 临界区的写者数量最多为1
    sem_init(&rCountMutex, 0, 1); //同一个时刻最多只能有一个线程修改读者数量
    pthread_t r, w;
    pthread_create(&r, NULL, reader, NULL);
    pthread_create(&w, NULL, writer, NULL);
    pthread_join(r, NULL);
    pthread_join(w, NULL);
    sem_destroy(&wMutex);
    sem_destroy(&rCountMutex);
  

上面,我们用了一个wMutex信号量来控制写者的互斥,用了一个rCountMutex信号量来控制对读者人数的互斥,一个int类型的rCount记录读者的数量。把写者写入数据前后分别加上P(wMutex)、V(wMutex),限制写者互斥。当读者修改读者数量时前后后加上P(rCountMutex)、V(rCountMutex),实现读者对读者人数修改的互斥,当第一位读者进入时,P(wMutex),阻塞写者写数据,当最后一位读者离开时,V(wMutex),唤醒写者写数据。这种方案是读者优先方案,可能会导致写者饥饿,一直被阻塞,因为一直有读者进入。如下图:
在这里插入图片描述
可以新增:
信号量 rMutex:控制读者进入的互斥信号量,初始值为 1;
写者计数 wCount:记录写者数量,初始值为 0;
信号量 wCountMutex:控制 wCount 互斥修改,初始值为 1;
实现写者优先方案(当读者可能一直处于饥饿状态)。

sem_t wMutex; //用于控制写操作的互斥信号量(为了只让一个写者写,不允许两个写者线程写数据)
sem_t rMutex;
sem_t rCountMutex; //用于控制对Rcount(读者修改)的修改为互斥操作,(不容许两个线程同时对Rcount变量操作)
sem_t wCountMutex;


int wCount = 0; //记录写者的数量
int rCount = 0; //记录读者的数量

void* writer()
{
    while(1)
    {
        
        sem_wait(&wCountMutex); //进入【wCount】临界区
        if(wCount == 0)
        {
            sem_wait(&rMutex); //当第一个写者进入,阻塞后面的读者
        }
        wCount++; //写者人数+1
        sem_post(&wCountMutex); //离开【wCount】临界区
		
		
		P(wMutex); //控制写者进入互斥
        printf("写者正在写入数据......");
		V(wMutex);

        sem_wait(&wCountMutex); //进入【wCount】临界区
        wCount--; //写者人数-1
        if(wCount == 0)
        {
            sem_post(&rMutex); //最后一个写者离开临界区,唤醒读者读入数据
        }
        sem_post(&wCountMutex); //离开【wCount】临界区
    }
}

void* reader()
{
    while(1)
    {
    	P(rMutex); //控制读者进入互斥
        sem_wait(&rCountMutex); //进入【rCount】临界区
        if(rCount == 0)
        {
            sem_wait(&wMutex); //不允许其它写者写数据,因为读者正在读取数据,必须阻塞写者
        }
        rCount++; //读者人数+1
        sem_post(&rCountMutex); //离开【rCount】临界区
		V(rMutex);
		
        printf("当前有%d位读者正在读取数据", rCount);
		

        sem_wait(&rCountMutex); //进入【rCount】临界区
        rCount--; //读者人数-1
        if(rCount == 0)
        {
            sem_post(&wMutex); //最后一个读者离开临界区,唤醒写者写入数据
        }
        sem_post(&rCountMutex); //离开【rCount】临界区

    }
} 

也可以新增:
信号量flag,初始值为1,实现公平策略。

sem_t wMutex; //用于控制写操作的互斥信号量(为了只让一个写者写,不允许两个写者线程写数据)
sem_t rCountMutex; //用于控制对Rcount(读者修改)的修改为互斥操作,(不容许两个线程同时对Rcount变量操作)
int rCount = 0; //记录读者的数量
sem_t falg;

void* writer()
{
    while(1)
    {
    	P(flag);
        sem_wait(&wMutex); //进入【写者写入数据的临界区】,不允许其它写者写数据
        printf("写者正在写入数据......");
        sem_post(&wMutex); //离开【写者写入数据的临界区】,允许其它写者写数据
        V(falg);
    }
}

void* reader()
{
    while(1)
    {
    	P(flag);
        sem_wait(&rCountMutex); //进入【rCount】临界区
        if(rCount == 0)
        {
            sem_wait(&wMutex); //不允许其它写者写数据,因为读者正在读取数据,必须阻塞写者
        }
        rCount++; //读者人数+1
        sem_post(&rCountMutex); //离开【rCount】临界区
		P(flag);
		
        printf("当前有%d位读者正在读取数据", rCount);


        sem_wait(&rCountMutex); //进入【rCount】临界区
        rCount--; //读者人数-1
        if(rCount == 0)
        {
            sem_post(&wMutex); //最后一个读者离开临界区,唤醒写者写入数据
        }
        sem_post(&rCountMutex); //离开【rCount】临界区

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值