同步机制—读者写者问题

article/2025/9/30 9:55:46

【实验目的】

      理解临界区和进程互斥的概念,掌握用信号量和PV操作实现进程互斥的方法。

【实验内容】

       在windows或者linux环境下编写一个控制台应用程序,该程序运行时能创建N个线程,其中既有读者线程又有写者线程,它们按照事先设计好的测试数据进行读写操作。请用信号量和PV操作实现读者/写者问题。

       读者/写者问题的描述如下:

       有一个被许多进程共享的数据区,这个数据区可以是一个文件,或者主存的一块空间,甚至可以是一组处理器寄存器。有一些只读取这个数据区的进程(reader)和一些只往数据区中写数据的进程(writer)。以下假设共享数据区是文件。这些读者和写者对数据区的操作必须满足以下条件:读—读允许;读—写互斥;写—写互斥。这些条件具体来说就是:

(1)任意多的读进程可以同时读这个文件;

(2)一次只允许一个写进程往文件中写;

(3)如果一个写进程正在往文件中写,禁止任何读进程或写进程访问文件;

(4)写进程执行写操作前,应让已有的写者或读者全部退出。这说明当有读者在读文件时不允许写者写文件。

对于读者-写者问题,有三种解决方法:

1、读者优先

       除了上述四个规则外,还增加读者优先的规定,当有读者在读文件时,对随后到达的读者和写者,要首先满足读者,阻塞写者。这说明只要有一个读者活跃,那么随后而来的读者都将被允许访问文件,从而导致写者长时间等待,甚至有可能出现写者被饿死的情况。

2、写者优先

       除了上述四个规则外,还增加写者优先的规定,即当有读者和写者同时等待时,首先满足写者。当一个写者声明想写文件时,不允许新的读者再访问文件。

3、无优先

       除了上述四个规则外,不再规定读写的优先权,谁先等待谁就先使用文件。

【实验原理】

【程序代码】

#include <windows.h>
#include <conio.h>
#include <stdlib.h>
#include <stdio.h>
#include <io.h>
#include <string.h>
#define MAX_THREAD 10           //待测试的线程数
typedef struct{                      //表示测试数据格式char thread_name[3];            //线程名unsigned int require_moment;    //请求操作时刻unsigned int persist_time;        //操作持续时间
}TEST_INFO;
TEST_INFO test_data[MAX_THREAD]={    //测试数据表{"r1",0,15},                          // r表示读者线程{"r2",1, 15},                         //w表示写者线程{"w1",3,3},{"r3",4, 2},{"w2",5,6},{"w3",6,10},{"r4",7,8},{"r5",9,2},{"w4",10,18},{"w5",12,2}
};int read_count=0;                     //记录正在读文件的读者数
int write_count=0;                   //在写者优先算法中记录声明要写文件的写者数
CRITICAL_SECTION CS_DATA;       //用于保护文件的临界区变量
HANDLE h_mutex_read_count=CreateMutex(NULL,FALSE,"mutex_read_count");
//读者计数器互斥体
HANDLE h_mutex_write_count=CreateMutex(NULL,FALSE,"mutex_write_count");
//写者计数器互斥体
HANDLE h_mutex_reader_wait=CreateMutex(NULL,FALSE,"mutex_reader_wait");
//在写者优先算法中用于阻塞读者的互斥体
HANDLE h_mutex_first_reader_wait=
CreateMutex(NULL,FALSE,"mutex_first_reader_wait");
//在写者优先算法中用于阻塞第一个读者的互斥体
HANDLE h_mutex_wait=CreateMutex(NULL,FALSE,"mutex_wait");
//无优先时用于阻塞读者和写者的互斥体//读者优先时的读者线程
void RF_reader_thread(void *data){char thread_name[3];                        //存放线程名称strcpy(thread_name,((TEST_INFO *)data)->thread_name);Sleep(((TEST_INFO *)data)->require_moment*1000);WaitForSingleObject(h_mutex_read_count,-1);
//申请进入关于读者计数器的临界区相当于P操作read_count++;if(read_count==1)EnterCriticalSection(&CS_DATA); //申请进入关于文件的临界区相当于P操作ReleaseMutex(h_mutex_read_count);
//离开关于读者计数器的临界区相当于V操作printf("%s ",thread_name);Sleep(((TEST_INFO *)data)->persist_time*1000); //用延迟相应秒来模拟读文件操作WaitForSingleObject(h_mutex_read_count,-1);read_count--;if(read_count==0)LeaveCriticalSection(&CS_DATA);//离开关于文件的临界区相当于V操作ReleaseMutex(h_mutex_read_count);
}//读者优先时的写者线程
void RF_writer_thread(void *data){Sleep(((TEST_INFO *)data)->require_moment*1000);//用延迟相应秒来模拟写文件操作EnterCriticalSection(&CS_DATA);//申请进入,相当于P操作 printf("%s ",((TEST_INFO *)data)->thread_name);Sleep(((TEST_INFO *)data)->persist_time*1000); //用延迟相应秒来模拟写文件操作LeaveCriticalSection(&CS_DATA);//离开,相当于V操作 
}//读者优先时的初始化程序
void reader_first(){int i=0;HANDLE h_thread[MAX_THREAD];printf("读优先申请次序:");for(i=0;i<MAX_THREAD;i++){printf("%s ",test_data[i].thread_name);};printf("\n");printf("读优先操作次序:");InitializeCriticalSection(&CS_DATA);            //初始化临界区变量for(i=0;i<MAX_THREAD;i++){           //根据线程名称创建不同的线程if(test_data[i].thread_name[0]=='r')  //名称的首字母是'r'则创建读者线程h_thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(RF_reader_thread),&test_data[i],0,NULL);else                           //名称的首字母是'w'则创建写者线程		h_thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(RF_writer_thread),&test_data[i],0,NULL);}WaitForMultipleObjects(MAX_THREAD,h_thread,TRUE,-1); //等待所有线程结束printf("\n");
}
//无优先时的读者线程
void FIFO_reader_thread(void *data){char thread_name[3];strcpy(thread_name,((TEST_INFO *)data)->thread_name);Sleep(((TEST_INFO *)data)->require_moment*1000);//用延迟相应秒来模拟写文件操作WaitForSingleObject(h_mutex_wait,-1);//申请进入 WaitForSingleObject(h_mutex_read_count,-1);//申请进入关于读者计数器的临界区相当于P操作read_count++;if(read_count==1)EnterCriticalSection(&CS_DATA);ReleaseMutex(h_mutex_read_count);//离开关于读者计数器的临界区相当于V操作ReleaseMutex(h_mutex_wait);//离开关于等待计数器的临界区相当于V操作printf("%s ",thread_name);Sleep(((TEST_INFO *)data)->persist_time*1000);//用延迟相应秒来模拟写文件操作WaitForSingleObject(h_mutex_read_count,-1);read_count--;if(read_count==0)LeaveCriticalSection(&CS_DATA);ReleaseMutex(h_mutex_read_count);//离开关于读者计数器的临界区相当于V操作
}//无优先时的写者线程
void FIFO_writer_thread(void *data){Sleep(((TEST_INFO *)data)->require_moment*1000);//用延迟相应秒来模拟写文件操作WaitForSingleObject(h_mutex_wait,-1);EnterCriticalSection(&CS_DATA);printf("%s ",((TEST_INFO *)data)->thread_name);Sleep(((TEST_INFO *)data)->persist_time*1000);//用延迟相应秒来模拟写文件操作LeaveCriticalSection(&CS_DATA);ReleaseMutex(h_mutex_wait);
}//无优先时的初始化程序
void first_come_first_served(){int i=0;HANDLE h_thread[MAX_THREAD];printf("无优先申请次序:");for(i=0;i<MAX_THREAD;i++){printf("%s ",test_data[i].thread_name);};printf("\n");printf("无优先操作次序:");InitializeCriticalSection(&CS_DATA);//初始化临界区变量for(i=0;i<MAX_THREAD;i++){//根据线程名称创建不同的线程if(test_data[i].thread_name[0]=='r')//名称的首字母是'r'则创建读者线程h_thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(FIFO_reader_thread),&test_data[i],0,NULL);else//名称的首字母是'w'则创建写者线程h_thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(FIFO_writer_thread),&test_data[i],0,NULL);}WaitForMultipleObjects(MAX_THREAD,h_thread,TRUE,-1); //等待所有线程结束printf("\n");
}//写者优先时的读者线程
void WF_reader_thread(void *data){char thread_name[3];strcpy(thread_name,((TEST_INFO *)data)->thread_name);Sleep(((TEST_INFO *)data)->require_moment*1000);//用延迟相应秒来模拟写文件操作WaitForSingleObject(h_mutex_reader_wait,-1);//申请进入,相当于P操作WaitForSingleObject(h_mutex_first_reader_wait,-1);//申请进入,相当于P操作WaitForSingleObject(h_mutex_read_count,-1);//申请进入,相当于P操作read_count++;if(read_count==1)EnterCriticalSection(&CS_DATA);ReleaseMutex(h_mutex_read_count);//离开,相当于V操作 ReleaseMutex(h_mutex_first_reader_wait);//离开,相当于V操作 ReleaseMutex(h_mutex_reader_wait);//离开,相当于V操作 printf("%s ",thread_name);Sleep(((TEST_INFO *)data)->persist_time*1000);//用延迟相应秒来模拟写文件操作WaitForSingleObject(h_mutex_read_count,-1);//申请进入,相当于P操作read_count--;if(read_count==0)LeaveCriticalSection(&CS_DATA);ReleaseMutex(h_mutex_read_count);//离开,相当于V操作 
}//写者优先时的写者线程
void WF_writer_thread(void *data){Sleep(((TEST_INFO *)data)->require_moment*1000);//用延迟相应秒来模拟写文件操作WaitForSingleObject(h_mutex_write_count,-1);if(write_count==0)WaitForSingleObject(h_mutex_first_reader_wait,-1);write_count++;ReleaseMutex(h_mutex_write_count);//离开关于写者计数器的临界区相当于V操作EnterCriticalSection(&CS_DATA);printf("%s ",((TEST_INFO *)data)->thread_name);Sleep(((TEST_INFO *)data)->persist_time*1000);//用延迟相应秒来模拟写文件操作LeaveCriticalSection(&CS_DATA);WaitForSingleObject(h_mutex_write_count,-1); //申请进入,相当于P操作write_count--;if(write_count==0)ReleaseMutex(h_mutex_first_reader_wait);ReleaseMutex(h_mutex_write_count);//离开关于写者计数器的临界区相当于V操作
}//写者优先时的初始化程序
void writer_first(){int i=0;HANDLE h_thread[MAX_THREAD];printf("写优先申请次序:");for(i=0;i<MAX_THREAD;i++){printf("%s ",test_data[i].thread_name);}printf("\n");printf("写优先操作次序:");InitializeCriticalSection(&CS_DATA);for(i=0;i<MAX_THREAD;i++){if(test_data[i].thread_name[0]=='r')h_thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(WF_reader_thread),&test_data[i],0,NULL);elseh_thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(WF_writer_thread),&test_data[i],0,NULL);}WaitForMultipleObjects(MAX_THREAD,h_thread,TRUE,-1);printf("\n");
}//主程序
int main(int argc,char *argv[]){char select;while(1){printf("|-----------------------------------|\n");printf("|  1:reader first          |\n");printf("|  2:first come first served |\n");printf("|  3:writer first           |\n");printf("|  4:exit                 |\n");printf("|-----------------------------------|\n");printf("select a function(1~4):");do{select=(char)getch();}while(select!='1'&&select!='2'&&select!='3'&&select!='4');system("cls");switch(select){case '1':reader_first();break;case '2':first_come_first_served();break;case '3':writer_first();break;case '4':return 0;}printf("\nPress any key to continue.");getch();system("cls");}return 0;
}

【实验结果】

图1 初始界面   

图2 读优先

图3 无优先

图4 写优先

【实验心得】

       通过本次实验,我对操作系统的同步机制---读者写者问题有了更加深刻的认识,实验中也学到了很多知识,受益匪浅。读者写者问题在计算机领域非常普遍,数据库的读写分离就是为了减少因为读者写者问题加锁带来的对并发的影响。

       读者写者问题的解决方案一般都有两种不同的侧重:读者优先或者写者优先。简单来说,读者优化就是尽量满足并发的读操作,当已经有线程在读数据的时候,其他读线程无需等待,而写线程需要等待所有正在进行的读操作之后才能执行。写者优先就是尽量满足写操作,尽管写操作不能并发,但是可以排队,优先于等待的读线程获得执行权。


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

相关文章

操作系统——读者写者问题

一、问题描述 要求: 1、允许多个读者可以同时对文件执行读操作。 2、只允许一个写者往文件中写信息。 3、任一写者在完成写操作之前不允许其他读者或写者工作。 4、写者执行写操作前,应让已有的读者和写者全部退出。 二、问题分析 读者写者问题最核心的问题是如何处理…

【操作系统-进程】PV操作——读者写者问题

文章目录 读者写者问题万能模板万能模板 1——读进程优先万能模板 2——读写公平法万能模板 3——写进程优先 题目 1&#xff1a;南北过桥问题题目 2&#xff1a;录像厅问题题目 3&#xff1a;更衣问题 读者写者问题万能模板 读者写者问题&#xff0c;其本质就是连续多个同类进…

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

目录 一、问题描述 二、解题思路 2.1 读者优先算法 2.2 写者优先算法 2.3 读写公平 三、源码实现 3.1 读者优先 3.2 写者优先 3.3 读写平等 一、问题描述 一个数据问价或记录可以被多个进程共享&#xff0c;我们把只读该文件的进程称为“读者进程”&#xff0c;其他进…

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&…