经典进程同步问题(三)——读者写者问题

article/2025/9/30 9:56:27

目录

一、问题描述

二、解题思路

2.1  读者优先算法

2.2 写者优先算法

2.3 读写公平

三、源码实现

3.1 读者优先

3.2 写者优先

3.3 读写平等


一、问题描述

        一个数据问价或记录可以被多个进程共享,我们把只读该文件的进程称为“读者进程”,其他进程为“写者进程”。允许多个进程同时读一个共享对象,但不允许一个写者进程和其他写者进程或读者进程同时访问共享对象。即:保证一个写者进程必须与其他进程互斥的访问共享对象的同步问题;读者-写者问题常用来测试新同步原语。

二、解题思路

        首先对于上述问题进行抽象:读者和写者是互斥的,写者和写者是互斥的,读者和读者不互斥;两类进程,一种是写者,另一种是读者。写者很好实现,因为它和其他任何进程都是互斥的,因此对每一个写者进程都给一个互斥信号量的P、V操作即可;而读者进程的问题就较为复杂,它与写者进程是互斥的,而又与其他的读者进程是同步的,因此不能简单的利用P、V操作解决。

下面我们给出三种方案来解决读者和写者之间、读者和读者之间、写者和写者之间的同步与互斥问题:

2.1  读者优先算法

        为实现Reader和Writer进程之间在读或写时的互斥而设置了一个互斥信号量wmutex。再设置一个整型变量conut表示正在读的进程数目。

  • 仅当count=0时,Reader进程才需要执行wait(wmutex)操作;同理,仅当Reader进程在执行了count减一操作后其值为0时,才需要执行signal(wmutex)操作,以便让Writer进程操作;
  • 由于count是一个可被多个Reader进程访问的临界资源,因此为其设置一个互斥信号量rmutex;

其伪码描述如下:

semaphore rmutex=1,wmutex=1;
int count=0;void Reader()
{while(1){wait(rmutex);if(count==0)wait(wmutex);count++;signal(rmutex);read;wait(rmutex);count--;if(count==0)signal(wmutex);signal(rmutex);}
}void Writer()
{while(1){wait(wmutex);write;signal(wmutex);}
}

在Linux系统环境下多次运行3.1中的源码,会出现以下情况:

         由于测试文本数目较小,仅试运行5次,当训练集数目较大时,则Writer亦有可能出现如上情况,即:等待Reader进程全部结束之后才逐步执行Writer进程。我们称这样的算法为读者优先算法,也就是说,当存在读进程时,写操作将被延迟,并且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。这样的方式下,会导致写进程可能长时间等待,且存在写进程“饿死”的情况。

2.2 写者优先算法

        所谓写者优先,即:当有读者进程正在执行,写者进程发出申请,这时应该拒绝其他读者进程的请求,等待当前读者进程结束后立即执行写者进程,只有在无写者进程执行的情况下才能够允许读者进程再次运行。为此,增加一个信号量并且在上面的程序中 writer()和reader()函数中各增加一对PV操作,就可以得到写进程优先的解决程序。

在读者优先的基础上

  • 增加信号量r,初值是1,用于禁止所有的读进程
  • 增加一个记数器,即整型变量writecount,记录写者数,初值是0(原count改为readcount)。 writecount为多个写者共享的变量,是临界资源。用互斥信号量wmutex控制, wmutex初值是1(原wmutex改为mutex1)。

伪码描述如下:

semaphore rmutex=1,wmutex=1,mutex1=1;
int readcount=0,writecount=0;void Reader()
{while(1){wait(r);wait(rmutex);if(count==0)wait(mutex1);count++;signal(rmutex);signal(r);                read;wait(rmutex);count--;if(count==0)signal(mutex1);signal(rmutex);}
}void Writer()
{while(1){wait(wmutex);writecount++;if(writecount==1)wait(r);signal(wmutex);wait(mutex1);write;signal(mutex1);wait(wmutex);writecount--;if(writecount==0)wait(r);signal(wmutex);signal(r);}
}

2.3 读写公平

为实现读写公平,我们必须要同时满足以下四个条件:

  1. 读者写者的优先级相同
  2. 读者、写者互斥访问
  3. 只允许有一个写者访问临界区
  4. 可以有多个读者同时访问临界区的资源

        为此,我们设置一个互斥信号量queue,其作用是让Writer进程和Reader进程进行排队,当有Reader进程执行时,queue=0,因此Writer进程会进入等待,直到queue变为1;当Writer进程执行时,同理;因此我们借助queue实现了读写进程的优先级平等。其余的保持3.1中算法不变。

在Linux环境下运行,可得结果:

 很明显的区别于3.1和3.2中的结果。

三、源码实现

3.1 读者优先

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define N 5int count=0,a=5,b=5;
int r[N]={0,1,2,3,4};
sem_t wmutex,rmutex;void delay()
{int time = rand() % 10 + 1;          //随机使程序睡眠0点几秒           usleep(time * 100000);
}void Reader(void *arg)
{int i=*(int *)arg;while(a>0){a--;delay();sem_wait(&rmutex);if(count==0)sem_wait(&wmutex);count++;sem_post(&rmutex);printf("Reader%d is reading!\n",i);printf("Reader%d reads end!\n",i);sem_wait(&rmutex);count--;if(count==0)sem_post(&wmutex);sem_post(&rmutex);}	
}void Writer()
{while(b>0){b--;delay();sem_wait(&wmutex);printf("writer is writing!\n");printf("writer writes end!\n");sem_post(&wmutex);}
}int main()
{int i;pthread_t writer,reader[N];srand((unsigned int)time(NULL));sem_init(&wmutex,0,1);//互斥锁初始化 sem_init(&rmutex,0,1);for(i=0;i<5;i++)//创建线程 {pthread_create(&reader[i],NULL,(void *)Reader,&r[i]);} pthread_create(&writer,NULL,(void *)Writer,NULL);pthread_join(writer,NULL);//线程等待 sem_destroy(&rmutex);   //互斥锁的销毁sem_destroy(&wmutex);   return 0;
} 

3.2 写者优先

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define N 5int readcount=0,writecount=0,a=5,b=2;
int r[N]={0,1,2,3,4};
int w[N]={0,1};
sem_t wmutex,rmutex,mutex1,num;void delay()
{int time = rand() % 10 + 1;          //随机使程序睡眠0点几秒           usleep(time * 100000);
}void Reader(void *arg)
{int i=*(int *)arg;while(a>0){a--;delay();//延迟 //进入共享文件前的准备 sem_wait(&num);//在无写者进程时进入 sem_wait(&rmutex);//与其他读者进程互斥的访问readcount if(readcount==0)sem_wait(&mutex1);//与写者进程互斥的访问共享文件 readcount++;sem_post(&rmutex);sem_post(&num); //readerprintf("Reader%d is reading!\n",i);printf("Reader%d reads end!\n",i);//退出共享文件后的处理 sem_wait(&rmutex);readcount--;if(readcount==0)sem_post(&mutex1);sem_post(&rmutex);}	
}void Writer(void *arg)
{int i=*(int *)arg;while(b>0){b--;delay();//进入共享文件前的准备 sem_wait(&wmutex);//保证多个写者进程能够互斥使用writecount writecount++;if(writecount==1)sem_wait(&num);//用于禁止读者进程 sem_post(&wmutex);//writersem_wait(&mutex1);//与其他所有进程互斥的访问共享文件 	printf("writer%d is writing!\n",i); printf("writer%d writes end!\n",i);sem_post(&mutex1);//退出共享文件后的处理 sem_wait(&wmutex);writecount--;if(writecount==0)sem_post(&num);sem_post(&wmutex);}
}int main()
{int i;pthread_t writer[N],reader[N];srand((unsigned int)time(NULL));sem_init(&wmutex,0,1);//互斥锁初始化 sem_init(&rmutex,0,1);sem_init(&mutex1,0,1);	sem_init(&num,0,1);for(i=0;i<5;i++)//创建线程 {pthread_create(&reader[i],NULL,(void *)Reader,&r[i]);} for(i=0;i<2;i++)//创建线程 {pthread_create(&writer[i],NULL,(void *)Writer,&w[i]);}for(i=0;i<2;i++)//等待线程 {pthread_join(writer[i],NULL);}for(i=0;i<5;i++)//等待线程 {pthread_join(reader[i],NULL);}	sem_destroy(&rmutex);   //互斥锁的销毁sem_destroy(&wmutex);   sem_destroy(&mutex1);sem_destroy(&num);return 0;
} 

3.3 读写平等

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>#define N 5int readcount=0,a=5,b=5;
int r[N]={0,1,2,3,4};
sem_t wmutex,rmutex,queue;void delay()
{int time = rand() % 10 + 1;          //随机使程序睡眠0点几秒           usleep(time * 100000);
}void Reader(void *arg)
{int i=*(int *)arg;while(a>0){a--;delay();sem_wait(&queue);		//让写者进程排队,读写进程具有相同的优先级 sem_wait(&rmutex);		//与其他读者进程互斥的访问readcount if(readcount==0)		//最开始的时候readcount=0 sem_wait(&wmutex);	//与写者进程互斥的访问共享文件 readcount++;sem_post(&rmutex);sem_post(&queue);		//使得写者进程进入准备状态 //Readerprintf("Reader%d is reading!\n",i);printf("Reader%d reads end!\n",i);sem_wait(&rmutex);readcount--;if(readcount==0)sem_post(&wmutex);sem_post(&rmutex);}	
}void Writer()
{while(b>0){b--;delay();sem_wait(&queue); sem_wait(&wmutex);printf("writer is writing!\n");printf("writer writes end!\n");sem_post(&wmutex);sem_post(&queue);}
}int main()
{int i;pthread_t writer,reader[N];srand((unsigned int)time(NULL));sem_init(&wmutex,0,1);//互斥锁初始化 sem_init(&rmutex,0,1);sem_init(&queue,0,1);for(i=0;i<5;i++)//创建线程 {pthread_create(&reader[i],NULL,(void *)Reader,&r[i]);} pthread_create(&writer,NULL,(void *)Writer,NULL);pthread_join(writer,NULL);//线程等待 sem_destroy(&rmutex);   //互斥锁的销毁sem_destroy(&wmutex);   sem_destroy(&queue); return 0;
} 

 


http://chatgpt.dhexx.cn/article/lSABsD5V.shtml

相关文章

2. 堆与栈的区别

2. 堆与栈的区别 在理解这两个概念时&#xff0c;需要放到具体的场景下&#xff0c;因为不同场景下&#xff0c;堆与栈代表不同的含义。一般情况下&#xff0c;有两层含义&#xff1a; &#xff08;1&#xff09;程序内存布局场景下&#xff0c;堆与栈表示的是两种内存管理方式…

栈与堆的区别(内存分配与数据结构)

参考自https://blog.csdn.net/K346K346/article/details/80849966/ 堆&#xff08;Heap&#xff09;与栈&#xff08;Stack&#xff09;包含两层含义&#xff1a; 程序内存布局场景下的内存管理方式数据结构中的两种常见的数据结构 1. 程序内存分配中的堆与栈 1.1 栈介绍 …

C语言里栈和堆的区别整理

这里说的是C语言程序内存分配中的堆和栈。下面先谈谈C语言的内存管理&#xff1a; 可执行程序在存储时&#xff08;没有调到内存&#xff09;分为代码区&#xff08;text&#xff09;、数据区&#xff08;data&#xff09;和未初始化数据区&#xff08;bss&#xff09;3个部分。…

看懂堆与栈的区别与联系

<div class"simditor-body clearfix" style"height: auto; overflow: inherit;"><h2> <strong>  堆和栈概要</strong></h2>在计算机领域&#xff0c;堆栈是一个不容忽视的概念&#xff0c;堆栈是两种数据结构。堆栈都是一…

C语言:栈和堆的区别

c语言五大内存分区 栈区&#xff08;stack&#xff09;:存放函数形参和局部变量&#xff08;auto类型&#xff09;&#xff0c;由编译器自动分配和释放 堆区&#xff08;heap&#xff09;:该区由程序员申请后使用&#xff0c;需要手动释放否则会造成内存泄漏。如果程序员没有手…

什么是栈(Stack)?什么是堆(Heap)?栈和堆的区别是什么?

原参考地址&#xff1a;https://zhidao.baidu.com/question/36918441.html 栈&#xff1a;由操作系统自动分配释放 &#xff0c;存放函数的参数值&#xff0c;局部变量du的值等。其操作方式类似于数据结构中的栈&#xff1b; 堆&#xff1a; 一般由程序员分配释放&#xff0c…

堆和栈的概念和区别

在说堆和栈之前&#xff0c;我们先说一下JVM&#xff08;虚拟机&#xff09;内存的划分&#xff1a; Java程序在运行时都要开辟空间&#xff0c;任何软件在运行时都要在内存中开辟空间&#xff0c;Java虚拟机运行时也是要开辟空间的。JVM运行时在内存中开辟一片内存区域&#x…

堆与栈的区别详细总结

1、堆与栈的区别详细总结_Fighting的博客-CSDN博客_堆和栈的区别 2、堆和栈的区别 - 江雨牧 - 博客园 3、堆和栈的区别_内外皆秀的博客-CSDN博客_堆和栈的区别 4、一文读懂堆与栈的区别_恋喵大鲤鱼的博客-CSDN博客_堆和栈的区别 一般情况下&#xff0c;如果有人把堆栈合起来…

栈和堆的区别

栈和堆的区别 前面已经介绍过&#xff0c;栈是由编译器在需要时分配的&#xff0c;不需要时自动清除的变量存储区。里面的变量通常是局部变量、函数参数等。堆是由malloc()函数分配的内存块&#xff0c;内存释放由程序员手动控制&#xff0c;在C语言为free函数完成。栈和堆的主…

Struts常见面试题

Struts分为Struts1和Struts2&#xff0c;默认情况下指的是Struts2&#xff0c;Struts1除非特别说明。 1、Struts2 中的 # 和 % 分别是做什么的&#xff1f; &#xff08;1&#xff09;使用#获取 context 里面的数据 <s:iterator value “list” var”user”> <s:p…

关于Struts2的笔试题(一)

一. struts2框架中struts.xml配置文件,package标签的属性有那几个?各有什么功能? 1.name属性 作用:定义一个包的名称&#xff0c;它必须唯一。 2.namespace属性 作用:主要是与action标签的name属性联合使用来确定一个action 的访问路径 3.extends属性 作用:指定继承自哪个…

Struts2详述一(struts2基础及2个核心)

临近大学毕业了&#xff0c;在毕业之前做点大学学到的知识和总结。如有哪些方面存在错误还望大神见谅。 首先&#xff0c;这里想从SSH这三大框架说起。首选从最简单的Struts2说起。这一篇我将讲述struts2一些基础及2个核心&#xff08;Action和result&#xff09;,下篇我们将着…

【面试】【Struts2常见问题总结】【02】

【常见面试问题总结目录>>>】 031 struts2如何对指定的方法进行验证&#xff1f; 1.validate()方法会校验action中所有与execute方法签名相同的方法&#xff1b;   2.要校验指定的方法通过重写validateXxx()方法实现&#xff0c; validateXxx()只会校验action中方法…

【面试】【Struts2常见问题总结】【01】

【常见面试问题总结目录>>>】 001 请简述struts1的工作流程和机制&#xff1a; Struts的工作流程:   在web应用启动时就会加载初始化ActionServlet,ActionServlet从   struts-config.xml文件中读取配置信息,把它们存放到各种配置对象   当ActionServlet接收到…

一道Struts面试题

题目是这样的 有两张表 一张为新闻类别表 有&#xff12;个字段&#xff1a; nid(pk) sort 有一张新闻内容表 有三个字段 cid(pk) nid(fk) title content 要求通过下拉列表框的方法选择新闻类别然后显示该类别的新闻标题&#xff08;在当前…

Java面试----2018年最新Struts2面试题

1、描述Struts2的工作原理 答:客户端发送请求--》请求经过一系列过滤器--》FilterDispatcher通过ActionMapper来决定这个Request需要调用哪个Action --》FilterDispatcher把请求的处理交给ActionProxy--》通过ConfigurationManager询问Struts配置文件&#xff08;Struts.xml&a…

Struts2面试问题

转载自 Struts2面试问题 1.什么是Struts2&#xff1f; Apache Struts2是一个用Java构建Web应用程序的开源框架。Struts2基于OpenSymphony WebWork框架。它从Struts1中得到了很大的改进&#xff0c;使其更加灵活&#xff0c;易于使用和扩展。Struts2的核心组件是Action&…

查看es的tcp和http端口

1、登录linux部署服务器&#xff0c;用命令查找配置文件elasticsearch.yml&#xff0c;如图 find -name elasticsearch.yml 2、进到elasticsearch.yml文件的目录 3、查看tcp&#xff0c;http端口

w10查看端口_Windows 10系统如何查看已打开的端口

最近Win10用户反映&#xff0c;电脑很经常中病毒&#xff0c;用户表示并没有在电脑上插入U盘&#xff0c;也没有打开不安全的网页&#xff0c;但就是一直中毒&#xff0c;这让用户非常苦恼。其实&#xff0c;出现这一问题&#xff0c;可能与电脑开启了一些端口有关&#xff0c;…

使用frp端口映射实现内网穿透(SSH、HTTP服务)

使用frp端口映射实现内网穿透(SSH、HTTP服务) 一、下载 通过内网穿透的原理和实现方式的学习我们已经明白了内网穿透的原理&#xff0c;想要实现内网穿透就需要让内网实现与具有公网IP的设备进行绑定。 我们这里使用frp&#xff08;一个专注于内网穿透的高性能的反向代理应用…