多线程编程示例4(写者读者问题)
读者写者也是一个非常著名的同步问题。
读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。
#pragma once #define _CRTDBG_MAP_ALLOC #include<cstdio> #include<Windows.h> #include<crtdbg.h> #include<process.h> int currreadernum = 0; const int readernum = 5; //关键段和事件 CRITICAL_SECTION cs,cs_readernum; HANDLE g_hEventWriter, g_hEventNoReader; //设置控制台输出颜色 BOOL SetConsolecolor(WORD wAttributes) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); if (hConsole == INVALID_HANDLE_VALUE) { return false; } return SetConsoleTextAttribute(hConsole, wAttributes); } //读者线程输出函数 void ReaderPrintFun(char *sz, ...) { va_list pArgList; va_start(pArgList, sz); EnterCriticalSection(&cs); SetConsolecolor(FOREGROUND_GREEN); vfprintf(stdout, sz, pArgList); LeaveCriticalSection(&cs); va_end(pArgList); } //写者线程输出函数 void WritePrintFun(char *sz) { EnterCriticalSection(&cs); SetConsolecolor(FOREGROUND_RED); printf("%s\n", sz); LeaveCriticalSection(&cs); } //读者线程函数 unsigned int _stdcall ReaderThreadFun(PVOID pM) { ReaderPrintFun(" 编号为%d的读者进入等待中...\n", GetCurrentThreadId()); //等待写者完成 WaitForSingleObject(g_hEventWriter, INFINITE); //读者个数增加 EnterCriticalSection(&cs_readernum); ++currreadernum; if (1==currreadernum) { ResetEvent(g_hEventNoReader); } LeaveCriticalSection(&cs_readernum); //读取文件 ReaderPrintFun("编号为%d的读者开始读取文件...\n", GetCurrentThreadId()); Sleep(rand() % 100); //结束阅读,读者个数减小 ReaderPrintFun("编号为%d的读者结束读取文件...\n", GetCurrentThreadId()); //读者个数减少 EnterCriticalSection(&cs_readernum); --currreadernum; if (0==currreadernum) { SetEvent(g_hEventNoReader); } LeaveCriticalSection(&cs_readernum); return 0; } //写者线程函数 unsigned int _stdcall WriteThreadFun(PVOID pM) { WritePrintFun("写者线程进入等待中..."); //等待读文件的读者为零 WaitForSingleObject(g_hEventNoReader, INFINITE); //标记写者正在写文件 ResetEvent(g_hEventWriter); WritePrintFun("写者开始写文件..."); Sleep(rand() % 100); WritePrintFun("写者结束写文件"); //标记写者结束写文件 SetEvent(g_hEventWriter); return 0; } int main() { printf("读者写者问题\n"); //初始化事件和信号量 InitializeCriticalSection(&cs); InitializeCriticalSection(&cs_readernum); //手动置位,已经触发 currreadernum = 0; g_hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL); g_hEventNoReader = CreateEvent(NULL, FALSE, TRUE, NULL); size_t i = 0; HANDLE handle[readernum + 1]; for ( i = 1; i < 3; i++) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); } //写者线程(置于此处是为了避免写者写完文件,然后就是顺序读取。。。) handle[0] = (HANDLE)_beginthreadex(NULL, 0, WriteThreadFun, NULL, 0, NULL); Sleep(50); //启动其余读者线程 for (; i < readernum + 1; i++) { handle[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL); } WaitForMultipleObjects(readernum + 1, handle, TRUE, INFINITE); for (i = 0; i < readernum + 1; i++) { CloseHandle(handle[i]); } //销毁事件和信号量 CloseHandle(g_hEventWriter); CloseHandle(g_hEventNoReader); DeleteCriticalSection(&cs); DeleteCriticalSection(&cs_readernum); //检测内存泄漏 _CrtDumpMemoryLeaks(); return 0; }
我原本想着是否使用信号量,后来看http://blog.csdn.net/morewindows/article/details/7596034
觉得使用事件确实也蛮不错的,关键是要对多线程的具体情景进行分析。
这里使用信号量的话,处理起来会比较麻烦。
运行结果:
此题亦可以通过读写锁SRWLock解决:
在写者线程调用AcquireSRWLockExclusive和ReleaseSRWLockExclusive;
在读者线程调用AcquireSRWLockShared和ReleaseSRWLockShared,代码量能够大大简化。