读者写者问题(写者优先)

本文介绍了使用C++17和C11标准实现读者写者问题的实验,通过类设计和PV操作解决读写互斥和写者优先的问题。实验中,读者和写者类继承自基类,并各自实现了特定的行为。在WindowsAPI下,通过信号量实现线程同步。读者在读取前会先检查是否有其他写者,而写者享有优先权,确保无读者时才能进行写操作。实验结果符合预期,展示出正确的读写互斥和写者优先原则。
摘要由CSDN通过智能技术生成

OS-读者写者实验

环境

语言标准:/std:c++17,/std:c11

基本逻辑

1.Writer与其他所有Reader、Writer互斥

2.Reader与Writer互斥,但与其他Reader不互斥

类设计

设计Reader、Writer类,包含属性、读写方法、其他方法等。

Myclass.h

参与者的基类

class Myclass 
{
public:
	string name;
	virtual void DoSth() = 0;
	virtual void Func() = 0;
	virtual void End() = 0;
};

Reader.h

读者类

class Reader : public Myclass
{
public:
	Reader()
	{
		name = "";
	}
	Reader(string s)
	{
		name = s;
	}
	void DoSth()
	{
		cout << name << " is doing something interesting." << "\n";
	}
	void Func()
	{
		cout << name << " is reading now..." << "\n";
	}
	void End()
	{
		cout << name << " has left" << "\n";
	}

}readers[5];

Writer.h

写者类

class Writer : public Myclass
{
public:
	Writer()
	{
		name = "";
	}
	Writer(string s)
	{
		name = s;
	}
	void DoSth()
	{
		cout << name << " is doing something interesting." << "\n";
	}
	void Func()
	{
		cout << name << " is writing something, please wait for a while..." << "\n";
	}
	void End()
	{
		cout << name << " has left" << "\n";
	}
}writers[5];

PV操作

PV.h

P
void P(void* S) //P操作,传入的参数是信号量
{
	WaitForSingleObject(S, INFINITE);
}

Windows API函数WaitForSingleObject,即等待线程结束,信号量S表示要等待的线程,即HANDLE。

参数dwMilliseconds是等待时间,为保证模拟真实,此处设定为INFINITE,即读者或写者会无限地等待下去。

V
void V(void* S) //V操作,传入的参数是信号量
{
	ReleaseSemaphore(S, 1, NULL);
}

传入目标线程的HANDLE,调用Windows API函数ReleaseSemaphore,这一函数通过句柄释放资源。

参数1 hSemaphore为目标线程句柄。

参数2 IReleaseCount为要增加信号量的数量,增加1,表示当前互斥量未被占用,与互斥量被创建时设置的初始信号量相同,表示资源未被占用。

参数3 lpPreviousCount返回前一次信号量,此处不需要,设为NULL即可。

互斥量
void* mutex = CreateSemaphoreW(NULL, 1, 1, NULL); //防止Count系数同时被多个Reader访问,保证Count数值正确,当mutex信号占用,当前的reader应当等待
void* rw = CreateSemaphoreW(NULL, 1, 1, NULL);  //保证读写互斥,当rw信号占用,当前的reader和writer都必须等待
void* w = CreateSemaphoreW(NULL, 1, 1, NULL);  //保证写优先,当w信号占用,当前的Reader应当等待

Windows API函数CreateSemephoreW,创建信号量。

参数1 IpSemaphoreAttributes是信号量的安全属性,此处设为NULL即可。

参数2 IInitialCount是初始化信号量,此处设为1,后续V操作也应当将信号量回归这一初始值,表示资源的释放。

参数3 IMaximumCount是信号量允许增加到的最大值。

参数4 IpName为信号量名称,设为NULL。

注意:其中Count系数用于记录当前程序中读者的数量,为保证每个读者获取或改变Count数值时是都是该时刻的真实值,应该保证Count系数被互斥访问。

Reader方法

是读者线程的线程函数。

读者线程函数包含三个部分:Beginning of Read,Read,End of Read

Beginning of Read:首先等待互斥量w、mutex,等待写者结束写以及其他读者结束对Count的操作。如果该读者是当前程序中唯一的读者,则占用互斥量rw,保证读写互斥。将主线程Sleep0.1秒,模拟Beginning of Read的过程。在这一过程中,程序中有且仅有当前读者占用了互斥量,即处在运行状态,其余线程应当均处在等待状态。更新Count数值,释放互斥量w、mutex,Beginning of Read结束。Beginning of Read过程与其余处在非Read状态的Reader线程以及Writer线程互斥

Read:模拟读者阅读过程,调用当前线程读者对象的功能方法,将主线程Sleep1秒,模拟阅读的过程。在这一过程中,当前程序中的互斥量被释放,可能存在多个读者并行。Read过程与写者线程互斥,但与其他读者不互斥

End of Read:首先等待互斥量mutex,等待其他读者结束对Count的操作。如果当前读者是程序中最后一位读者,那么释放互斥量rw,允许写者操作,更新count数值。模拟读者离开过程,将主线程Sleep0.1秒,模拟End of Read的过程。在这一过程中,程序中有且仅有当前读者占用了互斥量,即处在运行状态,其余线程应当均处在等待状态。释放mutex,End of Read结束。End of Read过程与处在非Read状态的Reader线程以及Writer线程互斥

unsigned long _stdcall Readers(int i)
{
	while (true)
	{
		//Beginning of Read
		P(w); //等待写进程结束,保证writer优先
		P(mutex); //等待其他Reader处理Count系数,保证Count数值正确
		Sleep(100);
		if (count == 0) //仅当前第一个Reader用于增加阻塞Writer的信号
		{
			P(rw);
		}
		
		count++; //当前Reader数量+1
		V(mutex); //Beginning of Read结束,释放mutex,count数值可以由下一个reader操作
		V(w); //释放w,即使此处释放了w,信号rw都必然存在,保证read过程中没有write出现。

		

		//Reading
		readers[i].Func(); //执行读者功能
		//Reader::f();
		//Reader::DoSth(); //读者其他功能
		Sleep(1000); //Wait for 1 second,体现reader的read过程



		//End of Read
		P(mutex); //等待其他Reader处理Count系数,保证Count数值正确
		readers[i].End();
		Sleep(100);
		count--; //读者数量-1
		if (count == 0) //如果此时没有Reader,那么释放rw,允许Writer执行写者功能
		{
			V(rw);
		}
		V(mutex); //End of Read结束,释放count
	}
	return (unsigned long)0;
}
Writer方法

是写者线程的线程函数。

写者线程函数包含三个部分:Beginning of Write,Write,End of Write

Beginning of Write:首先等待互斥量w、rw,保证写者间互斥、读写互斥。

Write:占用资源后,执行写者功能,将主线程Sleep1秒,模拟写的过程

End of Write:释放互斥量w、rw,将主线程Sleep0.1秒,模拟写者离开的过程

易得在写过程中,写线程全程与其余所有非主线程互斥

unsigned long _stdcall Writers(int i)
{
	while (true)
	{
		//Beginning of Write
		P(w); //等待其他写者
		P(rw); //等待当前所有线程结束


		//Writing
		writers[i].Func(); //执行写者功能
		//Writer::f();
		//Writer::DoSth(); //写者其他功能
		Sleep(1000); //wait for 1 second,体现writer的write过程


		//End of Write
		writers[i].End();
		Sleep(100);
		V(w); //释放w,完成写
		V(rw); //释放rw
	}
	return (unsigned long)0;
}

实验设计

创建对象

分别创建5个Reader、Writer对象。

for (int i = 0; i < 5; i++) //自定义五个reader writer
{
    string s = "Reader " + to_string(i + 1);
    readers[i] = { s };
    s = "Writer " + to_string(i + 1);
    writers[i] = { s };
}

创建线程

为五个Reader、Writer对象创建线程

#define LSR LPTHREAD_START_ROUTINE
for (int i = 0; i < 5; i++) //为五个reader writer创建线程
{
    reader[i] = CreateThread(NULL, 0, (LSR)Readers, (void*)i, 0, NULL);
}

for (int i = 0; i < 5; i++) 
{
    writer[i] = CreateThread(NULL, 0, (LSR)Writers, (void*)i, 0, NULL);
}

Windows API函数CreateThread,创建一个在调用进程的虚拟地址空间内执行的线程。

参数1 lpThreadAttributes 是线程安全属性,设为NULL,默认线程获取安全描述符,子进程将无法继承返回的句柄。

参数2 dwStackSize 指堆栈初始大小,设为0,则新线程使用可执行文件的默认大小。

参数3 lpStartAddress 是指向线程函数的指针,类型为LPTHREAD_START_ROUTINE。

参数4 IpParameter 是指向传递给线程函数的参数的指针。

参数5 dwCreationFlags 控制线程创建的数值,设为0,则该线程在创建后立即执行。若设置为CREATE_SUSPENDED(#define CREATE_SUSPENDED 0x00000004),则线程在执行ResumeThread函数前不会执行。

根据参数5可以重新设计创建线程的过程,线程的加入与否更加可控:

for (int i = 0; i < 5; i++) //为五个reader writer创建线程
{
    reader[i] = CreateThread(NULL, 0, (LSR)Readers, (void*)i, CREATE_SUSPENDED, NULL);
}

for (int i = 0; i < 5; i++) 
{
    writer[i] = CreateThread(NULL, 0, (LSR)Writers, (void*)i, CREATE_SUSPENDED, NULL);
}
for (int i = 0; i < 5; i++) //激活线程
{
    ResumeThread(reader[i]);
    ResumeThread(writer[i]);
}

参数6 IpThreadId 指向线程标识符变量的指针,设为NULL则不返回线程标识符。

线程并发

while (true) //线程并发运行
{
    for (int i = 0; i < 5; i++) //并发五个reader、writer
    {
        WaitForSingleObject(reader[i], INFINITE); //相当于p操作
        WaitForSingleObject(writer[i], INFINITE);
    }
}

预期结果

根据线程函数中的分析设计:Reader方法中,在beginning与end过程中reader线程与其余线程互斥,互斥过程主线程Sleep0.1秒,在read过程中reader间不互斥,read过程主线程Sleep1秒。Write方法中,writer全程与其余线程互斥,互斥过程主线程Sleep0.1秒,write过程中主线程Sleep1秒,write结束时主线程Sleep0.1秒。

那么实验预期结果应该符合以下情况:Writer与其余线程互斥,应当逐个执行,每个writer占用主线程一秒,根据写者优先,只有在一个writer结束后才会有其他线程。Reader在beginning过程中逐个执行,每个reader占用主线程0.1秒。read过程占用时间为1秒,这一过程中reader间不互斥,则可能出现多个reader同时执行,现象是:多个reader每隔0.1秒分别执行beginning,然后同时出现并占用1秒时间执行read。Reader在End过程中逐个执行,每个reader占用主线程0.1秒。且writer一定在所有reader和writer都执行end之后才执行。

整理得到:

1.读写互斥:只有reader全部结束才有writer的执行,writer未结束前不会有reader执行。

2.写者优先:只有当前writer结束后才会有其他线程。

3.Count占用互斥,reader不会同时出现,当前reader会在前一个reader结束beginning或结束end过程后再执行beginning或end过程。

运行代码,可见运行结果符合预期
在这里插入图片描述

对于连续的reader1 2 3 4 5,每隔0.1秒出现一行,然后共同等待1秒,然后每隔0.1秒出现一行end指令。对于writer,每个writer单独等待1秒,等待后执行end指令,只有end后才会有其他指令。且writer之前的所有reader均执行end指令。

实验优化

1.创建线程时设置参数dwCreationFlags为CREATE_SUSPENDED,在后续过程中通过ResumeThread方法控制个别线程是否加入并行。

2.设计随机数,在模拟read或模拟write过程设置随机Sleep时间,使得实验更加拟真。

附录

pch.h

#pragma once
#include <iostream>
#include <thread>
#include <iomanip>
#include <vector>
#include <Windows.h>
#include <stdlib.h>
#include <string>
#define count Count
#define LSR LPTHREAD_START_ROUTINE

#ifndef INFINITE
0xFFFFFFFF
#endif

#ifndef NULL
0
#endif

using namespace std;
int Count = 0;

main.cpp

#include "pch.h"
#include "Writer.h"
#include "Reader.h"
#include "PV.h"

int main()
{
	ios::sync_with_stdio(false);
	HANDLE reader[5];
	HANDLE writer[5];

	unsigned int X = std::thread::hardware_concurrency();
	cout << "The maximum of concurrent threads of the hardware in current device: ";
	cout << std::thread::hardware_concurrency() << endl;

	//cout << "The main thread will PAUSE..." << endl;
	//Sleep(2000);

	cout << "Start simulating the reader/writer process." << endl;
	system("pause");

	for (int i = 0; i < 5; i++) //自定义五个reader writer
	{
		string s = "Reader " + to_string(i + 1);
		readers[i] = { s };
		s = "Writer " + to_string(i + 1);
		writers[i] = { s };
	}

	for (int i = 0; i < 5; i++) //为五个reader writer创建线程
	{
		reader[i] = CreateThread(NULL, 0, (LSR)Readers, (void*)i, 0, NULL);
	}

	for (int i = 0; i < 5; i++) 
	{
		writer[i] = CreateThread(NULL, 0, (LSR)Writers, (void*)i, 0, NULL);
	}

	/*for (int i = 0; i < 5; i++)
	{
		ResumeThread(reader[i]);
		ResumeThread(writer[i]);
	}*/
	while (true) //线程并发运行
	{
		for (int i = 0; i < 5; i++)
		{
			WaitForSingleObject(reader[i], INFINITE);
			WaitForSingleObject(writer[i], INFINITE);
		}
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值