读者写者问题(有bug 后续更改)
与上一篇《秒杀多线程第十篇 生产者消费者问题》的生产者消费者问题一样,读者写者也是一个非常著名的同步问题。读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。
上面是读者写者问题示意图,类似于生产者消费者问题的分析过程,首先来找找哪些是属于“等待”情况。
第一.写者要等到没有读者时才能去写文件。
第二.所有读者要等待写者完成写文件后才能去读文件。
找完“等待”情况后,再看看有没有要互斥访问的资源。由于只有一个写者而读者们是可以共享的读文件,所以按题目要求并没有需要互斥访问的资源。类似于上一篇中美观的彩色输出,我们对生产者输出代码进行了颜色设置(在控制台输出颜色设置参见《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;
- }
运行结果如下所示:
根据结果可以看出当有读者在读文件时,写者线程会进入等待状态中。当写者线程在写文件时,读者线程也会排队等待,说明读者和写者已经完成了同步。
本系列通过经典线程同步问题来列举线程同步手段的关键段、事件、互斥量、信号量,并作对这四种方法进行了总结。然后又通过二个著名的线程同步实例——生产者消费者问题和读者写者问题来强化对多线程同步互斥的理解与运用。希望读者们能够熟练掌握,从而在笔试面试中能够顺利的“秒杀”多线程的相关试题,获得自己满意的offer。
从《秒杀多线程第十篇生产者消费者问题》到《秒杀多线程第十一篇读者写者问题》可以得出多线程问题的关键在于找到所有“等待”情况和判断有无需要互斥访问的资源。那么如何从实际问题中更好更快更全面的找出这些了?请看《秒杀多线程第十二篇多线程同步内功心法——PV操作上》和《秒杀多线程第十三篇多线程同步内功心法——PV操作下》这二篇以加强解决多线程同步问题的“内功”。
另外,读者写者问题可以用读写锁SRWLock来解决,请看《秒杀多线程第十四篇 读者写者问题继 读写锁SRWLock》
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7596034
读者写者问题(有bug 后续更改)的更多相关文章
- Java实现生产者消费者问题与读者写者问题
摘要: Java实现生产者消费者问题与读者写者问题 1.生产者消费者问题 生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从 ...
- Linux多线程实践(6) --Posix读写锁解决读者写者问题
Posix读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *rest ...
- OS: 读者写者问题(写者优先+LINUX+多线程+互斥量+代码)(转)
一. 引子 最近想自己写个简单的 WEB SERVER ,为了先练练手,熟悉下在LINUX系统使用基本的进程.线程.互斥等,就拿以前学过的 OS 问题开开刀啦.记得当年学读者写者问题,尤其是写者优先的 ...
- 读者写者问题继 读写锁SRWLock
在<秒杀多线程第十一篇读者写者问题>文章中我们使用事件和一个记录读者个数的变量来解决读者写者问题.问题虽然得到了解决,但代码有点复杂.本篇将介绍一种新方法--读写锁SRWLock来解决这一 ...
- 多线程面试题系列(14):读者写者问题继 读写锁SRWLock
在第十一篇文章中我们使用事件和一个记录读者个数的变量来解决读者写者问题.问题虽然得到了解决,但代码有点复杂.本篇将介绍一种新方法--读写锁SRWLock来解决这一问题.读写锁在对资源进行保护的同时,还 ...
- 转---秒杀多线程第十四篇 读者写者问题继 读写锁SRWLock
在<秒杀多线程第十一篇读者写者问题>文章中我们使用事件和一个记录读者个数的变量来解决读者写者问题.问题虽然得到了解决,但代码有点复杂.本篇将介绍一种新方法——读写锁SRWLock来解决这一 ...
- linux 读者/写者自旋锁
内核提供了一个自旋锁的读者/写者形式, 直接模仿我们在本章前面见到的读者/写者旗标. 这些锁允许任何数目的读者同时进入临界区, 但是写者必须是排他的存取. 读者写者锁有 一个类型 rwlock_t, ...
- linux 读者/写者旗标
旗标为所有调用者进行互斥, 不管每个线程可能想做什么. 然而, 很多任务分为 2 种清 楚的类型: 只需要读取被保护的数据结构的类型, 和必须做改变的类型. 允许多个并发读 者常常是可能的, 只要没有 ...
- 用Objective-C写了一个简单的批量更改文件名的程序
前言:因为本人要高仿一个app,从app中解压asserts得到的所有图片文件,文件名都带有~iPhone这个干扰的名字,为了去除这个~iPhone这个字符串,所以本人写了个简答的批量更改所有文件名的 ...
随机推荐
- JavaScript的事件对象_事件流
事件流事件流是描述的从页面接受事件的顺序,当几个都具有事件的元素层叠在一起的时候,那么你点击其中一个元素,并不是只有当前被点击的元素会触发事件,而层叠在你点击范围的所有元素都会触发事件.事件流包括两种 ...
- 配置Java EE Eclipse+Tomcat开发环境
以下将详细介绍在Eclipse下搭建Java EE开发环境的每一步, 环境:Win 7 + JDK 1.7 + Eclipse IDE for Java EE Developers 3.7 +Tomc ...
- HEVC编解码优化信息
hevc解码GPU优化: 1.This GPU acceleration has opened up the possibility of playing a Full-HD (1080p) at 6 ...
- Unix domain sockets
#server: SERVER_PATH = "/tmp/python_unix_socket_server" def run_unix_domain_socket_server( ...
- VB6 GDI+ 入门教程[7] Graphics 其他内容
http://vistaswx.com/blog/article/category/tutorial/page/2 VB6 GDI+ 入门教程[7] Graphics 其他内容 2009 年 9 月 ...
- MariaDB 加密特性及使用方法
版权声明:本文由吴洪辉原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/193 来源:腾云阁 https://www.qclo ...
- java 字符串(正则表达式)未完
正则表达式: 其实就是用于操作字符串的一个规则.(以某种方式描述字符串) 基础: 1.描述一个整数:\d(表示一位数字) \\d(\\ 表示要插入一个正则表达式)表示一位数字 \\\\ 插入一个普通 ...
- 为什么MB51本位币金额和采购订单历史本位币金额不一样?
翻译 为什么物料凭证的本位币金额有时和采购订单历史记录物料的本位币金额不一致? 回答: 有2个字段对应FI存放位置.物料凭证的字段(MSEG-DMBER)是存放库存账目金额.采购订单历史(EKBE-D ...
- EF Core » 关系
对初学者理解关系很有用,先留下来,有时间边看边翻译. Caution 注意 This documentation is for EF Core. For EF6.x and earlier relea ...
- DirectX中的纹理及其创建
正如大多初学者会遇到一个问题, 导入的图片为何不是原来的尺寸?例如800*600的实际上通过D3DXCreateTextureFromFile后变成的是1024*1024,即宽和高默认都会自动扩展为2 ...