多线程11:读者写者问题

与上一篇《秒杀多线程第十篇 生产者消费者问题》的生产者消费者问题一样,读者写者也是一个非常著名的同步问题。读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。

上面是读者写者问题示意图,类似于生产者消费者问题的分析过程,首先来找找哪些是属于“等待”情况。

第一.写者要等到没有读者时才能去写文件。

第二.所有读者要等待写者完成写文件后才能去读文件。

找完“等待”情况后,再看看有没有要互斥访问的资源。由于只有一个写者而读者们是可以共享的读文件,所以按题目要求并没有需要互斥访问的资源。类似于上一篇中美观的彩色输出,我们对生产者输出代码进行了颜色设置(在控制台输出颜色设置参见《VC 控制台颜色设置》)。因此在这里要加个互斥访问,不然很有可能在写者线程将控制台颜色设置还原之前,读者线程就已经有输出了。所以要对输出语句作个互斥访问处理,修改后的读者及写者的输出函数如下所示:

//读者线程输出函数
void ReaderPrintf(char *pszFormat, ...)
{
	va_list   pArgList;
	va_start(pArgList, pszFormat);
	EnterCriticalSection(&g_cs);
	vfprintf(stdout, pszFormat, pArgList);
	LeaveCriticalSection(&g_cs);
	va_end(pArgList);
}
//写者线程输出函数
void WriterPrintf(char *pszStr)
{
	EnterCriticalSection(&g_cs);
	SetConsoleColor(FOREGROUND_GREEN);
	printf("     %s\n", pszStr);
	SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
	LeaveCriticalSection(&g_cs);
}

读者线程输出函数所使用的可变参数详见《C,C++中使用可变参数》。

       解决了互斥输出问题,接下来再考虑如何实现同步问题。可以设置一个变量来记录正在读文件的读者个数,第一个开始读文件的读者要负责将关闭允许写者进入的标志,最后一个结束读文件的读者要负责打开允许写者进入的标志。这样第一种“等待”情况就解决了。第二种“等待”情况是有写者进入时所以读者不能进入,使用一个事件就可以完成这个任务了——所有读者都要等待这个事件而写者负责触发事件和设置事件为未触发。详细见代码中注释:

//读者与写者问题
#include <stdio.h>
#include <process.h>
#include <windows.h>
//设置控制台输出颜色
BOOL SetConsoleColor(WORD wAttributes)
{
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	if (hConsole == INVALID_HANDLE_VALUE)
		return FALSE;
	
	return SetConsoleTextAttribute(hConsole, wAttributes);
}
const int READER_NUM = 5;  //读者个数
//关键段和事件
CRITICAL_SECTION g_cs, g_cs_writer_count;
HANDLE g_hEventWriter, g_hEventNoReader;
int g_nReaderCount;
//读者线程输出函数(变参函数的实现)
void ReaderPrintf(char *pszFormat, ...)
{
	va_list   pArgList;
	
	va_start(pArgList, pszFormat);
	EnterCriticalSection(&g_cs);
	vfprintf(stdout, pszFormat, pArgList);
	LeaveCriticalSection(&g_cs);
	va_end(pArgList);
}
//读者线程函数
unsigned int __stdcall ReaderThreadFun(PVOID pM)
{
	ReaderPrintf("     编号为%d的读者进入等待中...\n", GetCurrentThreadId());
	//等待写者完成
	WaitForSingleObject(g_hEventWriter, INFINITE);

	//读者个数增加
	EnterCriticalSection(&g_cs_writer_count);
	g_nReaderCount++;
	if (g_nReaderCount == 1)
		ResetEvent(g_hEventNoReader);
	LeaveCriticalSection(&g_cs_writer_count);

	//读取文件
	ReaderPrintf("编号为%d的读者开始读取文件...\n", GetCurrentThreadId());

	Sleep(rand() % 100);

	//结束阅读,读者个数减小,空位增加
	ReaderPrintf(" 编号为%d的读者结束读取文件\n", GetCurrentThreadId());

	//读者个数减少
	EnterCriticalSection(&g_cs_writer_count);
	g_nReaderCount--;
	if (g_nReaderCount == 0)
		SetEvent(g_hEventNoReader);
	LeaveCriticalSection(&g_cs_writer_count);

	return 0;
}
//写者线程输出函数
void WriterPrintf(char *pszStr)
{
	EnterCriticalSection(&g_cs);
	SetConsoleColor(FOREGROUND_GREEN);
	printf("     %s\n", pszStr);
	SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
	LeaveCriticalSection(&g_cs);
}
//写者线程函数
unsigned int __stdcall WriterThreadFun(PVOID pM)
{
	WriterPrintf("写者线程进入等待中...");
	//等待读文件的读者为零
	WaitForSingleObject(g_hEventNoReader, INFINITE);
	//标记写者正在写文件
	ResetEvent(g_hEventWriter);
		
	//写文件
	WriterPrintf("  写者开始写文件.....");
	Sleep(rand() % 100);
	WriterPrintf("  写者结束写文件");

	//标记写者结束写文件
	SetEvent(g_hEventWriter);
	return 0;
}
int main()
{
	printf("  读者写者问题\n");
	printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");

	//初始化事件和信号量
	InitializeCriticalSection(&g_cs);
	InitializeCriticalSection(&g_cs_writer_count);

	//手动置位,初始已触发
	g_hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL);
	g_hEventNoReader  = CreateEvent(NULL, FALSE, TRUE, NULL);
	g_nReaderCount = 0;

	int i;
	HANDLE hThread[READER_NUM + 1];
	//先启动二个读者线程
	for (i = 1; i <= 2; i++)
		hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
	//启动写者线程
	hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WriterThreadFun, NULL, 0, NULL);
	Sleep(50);
	//最后启动其它读者结程
	for ( ; i <= READER_NUM; i++)
		hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
	WaitForMultipleObjects(READER_NUM + 1, hThread, TRUE, INFINITE);
	for (i = 0; i < READER_NUM + 1; i++)
		CloseHandle(hThread[i]);

	//销毁事件和信号量
	CloseHandle(g_hEventWriter);
	CloseHandle(g_hEventNoReader);
	DeleteCriticalSection(&g_cs);
	DeleteCriticalSection(&g_cs_writer_count);
	return 0;
}

运行结果如下所示:

根据结果可以看出当有读者在读文件时,写者线程会进入等待状态中。当写者线程在写文件时,读者线程也会排队等待,说明读者和写者已经完成了同步。

 

以上程序有些复杂,如果直接用信号量来解决问题会简化很多:

#include<iostream>
#include <process.h>
#include <windows.h>
using namespace std;
const int nReader=3;//读者数量
const int WRITE_NUMBER=2;//写者需要写入的次数
int g_File,g_i;
int nWriterCount=0,nReaderCount=0;
CRITICAL_SECTION g_cs;
HANDLE g_hReader,g_hWriter,g_hEvent;

BOOL SetConsoleColor(WORD dw)
{
	HANDLE hConsole=GetStdHandle(STD_OUTPUT_HANDLE);
	if (hConsole==INVALID_HANDLE_VALUE)
	{
		return FALSE;
	}
	return SetConsoleTextAttribute(hConsole,dw);
}
void ReaderPrintf(int threadID,char* text)  //读者的输出
{  	  
	EnterCriticalSection(&g_cs);  
	cout<<"线程"<<threadID<<text<<endl;
	LeaveCriticalSection(&g_cs);   
}  

void WriterPrintf(char* text)   ////写者的输出
{  	  
	EnterCriticalSection(&g_cs);  
	cout<<text<<endl;
	LeaveCriticalSection(&g_cs);   
}  

unsigned int __stdcall ReaderThread(LPVOID pM)
{
	while (nReaderCount<nReader*WRITE_NUMBER) //读者总共需要读取nReader*WRITE_NUMBER次 
        {
		
		nReaderCount++;
		WaitForSingleObject(g_hReader,INFINITE);
		ReaderPrintf(GetCurrentThreadId(),"开始读取...");  
		Sleep(rand()%100);
		ReaderPrintf(GetCurrentThreadId(),"读取结束");  
		ReleaseSemaphore(g_hWriter,1,NULL);

	}	    	
	return 0;
}

unsigned int __stdcall WriterThread(LPVOID pM)
{
	
	while(nWriterCount<WRITE_NUMBER) ////写者共需要写入WRITE_NUMBER次
	{
		
		nWriterCount++;
		
		WaitForSingleObject(g_hWriter,INFINITE);
		WaitForSingleObject(g_hWriter,INFINITE);
		WaitForSingleObject(g_hWriter,INFINITE);
		

		SetConsoleColor(FOREGROUND_GREEN);
		g_File=g_i;

		WriterPrintf("写线程开始写入...");
		Sleep(rand()%100);
		WriterPrintf("写线程结束写入");

		SetConsoleColor(FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE); 
		g_i++;
		ReleaseSemaphore(g_hReader,3,NULL);
		//Sleep(100);

	}
	
	
	return 0;	
	
}

int main()
{
	InitializeCriticalSection(&g_cs);

	g_hReader=CreateSemaphore(NULL,0,nReader,NULL); //初始化读者的信号量,未触发
	g_hWriter=CreateSemaphore(NULL,nReader,nReader,NULL); //初始化写者的信号量,当前值和最大值都为nReader,已触发	HANDLE hThread[nReader+1];
	g_File=0,g_i=0;

	hThread[0]=(HANDLE)_beginthreadex(NULL,0,WriterThread,NULL,0,NULL);
	for (int i=1;i<=nReader;i++)
	{
		hThread[i]=(HANDLE)_beginthreadex(NULL,0,ReaderThread,NULL,0,NULL);
	}
	
	WaitForMultipleObjects(nReader+1,hThread,TRUE,INFINITE);
	cout<<"=====================================\n读写结束"<<endl;
	for (int i=0;i<nReader+1;i++)
	{
		CloseHandle(hThread[i]);

	}

	CloseHandle(g_hReader);
	CloseHandle(g_hWriter);
	return 0;
}


本系列通过经典线程同步问题来列举线程同步手段的关键段事件互斥量信号量,并作对这四种方法进行了总结。然后又通过二个著名的线程同步实例——生产者消费者问题读者写者问题来强化对多线程同步互斥的理解与运用。希望读者们能够熟练掌握,从而在笔试面试中能够顺利的“秒杀”多线程的相关试题,获得自己满意的offer

 

部分内容转自http://blog.csdn.net/morewindows/article/details/7596034

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值