秒杀多线程第十一篇 读者写者问题

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

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

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

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

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

[cpp] view plain copy

  1. //读者线程输出函数
  2. void ReaderPrintf(char *pszFormat, ...)
  3. {
  4. va_list   pArgList;
  5. va_start(pArgList, pszFormat);
  6. EnterCriticalSection(&g_cs);
  7. vfprintf(stdout, pszFormat, pArgList);
  8. LeaveCriticalSection(&g_cs);
  9. va_end(pArgList);
  10. }
  11. //写者线程输出函数
  12. void WriterPrintf(char *pszStr)
  13. {
  14. EnterCriticalSection(&g_cs);
  15. SetConsoleColor(FOREGROUND_GREEN);
  16. printf("     %s\n", pszStr);
  17. SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
  18. LeaveCriticalSection(&g_cs);
  19. }

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

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

[cpp] view plain copy

  1. //读者与写者问题
  2. #include <stdio.h>
  3. #include <process.h>
  4. #include <windows.h>
  5. //设置控制台输出颜色
  6. BOOL SetConsoleColor(WORD wAttributes)
  7. {
  8. HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  9. if (hConsole == INVALID_HANDLE_VALUE)
  10. return FALSE;
  11. return SetConsoleTextAttribute(hConsole, wAttributes);
  12. }
  13. const int READER_NUM = 5;  //读者个数
  14. //关键段和事件
  15. CRITICAL_SECTION g_cs, g_cs_writer_count;
  16. HANDLE g_hEventWriter, g_hEventNoReader;
  17. int g_nReaderCount;
  18. //读者线程输出函数(变参函数的实现)
  19. void ReaderPrintf(char *pszFormat, ...)
  20. {
  21. va_list   pArgList;
  22. va_start(pArgList, pszFormat);
  23. EnterCriticalSection(&g_cs);
  24. vfprintf(stdout, pszFormat, pArgList);
  25. LeaveCriticalSection(&g_cs);
  26. va_end(pArgList);
  27. }
  28. //读者线程函数
  29. unsigned int __stdcall ReaderThreadFun(PVOID pM)
  30. {
  31. ReaderPrintf("     编号为%d的读者进入等待中...\n", GetCurrentThreadId());
  32. //等待写者完成
  33. WaitForSingleObject(g_hEventWriter, INFINITE);
  34. //读者个数增加
  35. EnterCriticalSection(&g_cs_writer_count);
  36. g_nReaderCount++;
  37. if (g_nReaderCount == 1)
  38. ResetEvent(g_hEventNoReader);
  39. LeaveCriticalSection(&g_cs_writer_count);
  40. //读取文件
  41. ReaderPrintf("编号为%d的读者开始读取文件...\n", GetCurrentThreadId());
  42. Sleep(rand() % 100);
  43. //结束阅读,读者个数减小,空位增加
  44. ReaderPrintf(" 编号为%d的读者结束读取文件\n", GetCurrentThreadId());
  45. //读者个数减少
  46. EnterCriticalSection(&g_cs_writer_count);
  47. g_nReaderCount--;
  48. if (g_nReaderCount == 0)
  49. SetEvent(g_hEventNoReader);
  50. LeaveCriticalSection(&g_cs_writer_count);
  51. return 0;
  52. }
  53. //写者线程输出函数
  54. void WriterPrintf(char *pszStr)
  55. {
  56. EnterCriticalSection(&g_cs);
  57. SetConsoleColor(FOREGROUND_GREEN);
  58. printf("     %s\n", pszStr);
  59. SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
  60. LeaveCriticalSection(&g_cs);
  61. }
  62. //写者线程函数
  63. unsigned int __stdcall WriterThreadFun(PVOID pM)
  64. {
  65. WriterPrintf("写者线程进入等待中...");
  66. //等待读文件的读者为零
  67. WaitForSingleObject(g_hEventNoReader, INFINITE);
  68. //标记写者正在写文件
  69. ResetEvent(g_hEventWriter);
  70. //写文件
  71. WriterPrintf("  写者开始写文件.....");
  72. Sleep(rand() % 100);
  73. WriterPrintf("  写者结束写文件");
  74. //标记写者结束写文件
  75. SetEvent(g_hEventWriter);
  76. return 0;
  77. }
  78. int main()
  79. {
  80. printf("  读者写者问题\n");
  81. printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");
  82. //初始化事件和信号量
  83. InitializeCriticalSection(&g_cs);
  84. InitializeCriticalSection(&g_cs_writer_count);
  85. //手动置位,初始已触发
  86. g_hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL);
  87. g_hEventNoReader  = CreateEvent(NULL, FALSE, TRUE, NULL);
  88. g_nReaderCount = 0;
  89. int i;
  90. HANDLE hThread[READER_NUM + 1];
  91. //先启动二个读者线程
  92. for (i = 1; i <= 2; i++)
  93. hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
  94. //启动写者线程
  95. hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WriterThreadFun, NULL, 0, NULL);
  96. Sleep(50);
  97. //最后启动其它读者结程
  98. for ( ; i <= READER_NUM; i++)
  99. hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);
  100. WaitForMultipleObjects(READER_NUM + 1, hThread, TRUE, INFINITE);
  101. for (i = 0; i < READER_NUM + 1; i++)
  102. CloseHandle(hThread[i]);
  103. //销毁事件和信号量
  104. CloseHandle(g_hEventWriter);
  105. CloseHandle(g_hEventNoReader);
  106. DeleteCriticalSection(&g_cs);
  107. DeleteCriticalSection(&g_cs_writer_count);
  108. return 0;
  109. }

运行结果如下所示:

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

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

从《秒杀多线程第十篇生产者消费者问题》到《秒杀多线程第十一篇读者写者问题》可以得出多线程问题的关键在于找到所有“等待”情况和判断有无需要互斥访问的资源。那么如何从实际问题中更好更快更全面的找出这些了?请看《秒杀多线程第十二篇多线程同步内功心法——PV操作上》和《秒杀多线程第十三篇多线程同步内功心法——PV操作下》这二篇以加强解决多线程同步问题的“内功”。

另外,读者写者问题可以用读写锁SRWLock来解决,请看《秒杀多线程第十四篇 读者写者问题继 读写锁SRWLock

转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7596034

时间: 2024-10-12 22:14:16

秒杀多线程第十一篇 读者写者问题的相关文章

转---秒杀多线程第十一篇 读者写者问题

与上一篇<秒杀多线程第十篇 生产者消费者问题>的生产者消费者问题一样,读者写者也是一个非常著名的同步问题.读者写者问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件. 上面是读者写者问题示意图,类似于生产者消费者问题的分析过程,首先来找找哪些是属于“等待”情况. 第一.写者要等到没有读者时才能去写文件. 第二.所有读者要等待写者完成写文件后才能去读文件. 找完“等待”情况后,再看看有没有要互斥访问的资源.由

秒杀多线程第十一篇 读者写者问题(续)

java实现: 本问题的关键是读者写者之间的同步问题,尤其使用java来操作. 1.等待读者,使用CountDownLatch mReaderLatch, 但是CountDownLatch只能使用一次,所以需要每次都new 一个. 或者可以考虑使用semaphore代替,但是semaphore需要acquire(READ_THREAD_SIZE)才能等待所有读者线程结束. 2.等待写入操作.使用semaphore来控制, mWriteSema.release(READ_THREAD_SIZE);

转---秒杀多线程第十四篇 读者写者问题继 读写锁SRWLock

在<秒杀多线程第十一篇读者写者问题>文章中我们使用事件和一个记录读者个数的变量来解决读者写者问题.问题虽然得到了解决,但代码有点复杂.本篇将介绍一种新方法——读写锁SRWLock来解决这一问题.读 写锁在对资源进行保护的同时,还能区分想要读取资源值的线程(读取者线程)和想要更新资源的线程(写入者线程).对于读取者线程,读写锁会允许他们并发的 执行.当有写入者线程在占有资源时,读写锁会让其它写入者线程和读取者线程等待.因此用读写锁来解决读者写者问题会使代码非常清晰和简洁. 下面就来看看如何使用读

转----秒杀多线程第十篇 生产者消费者问题

继经典线程同步问题之 后,我们来看看生产者消费者问题及读者写者问题.生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给 若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从 缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中 再次投放产品. 这个生产者消费者题目不仅

秒杀多线程第十篇 生产者消费者问题

继经典线程同步问题之后,我们来看看生产者消费者问题及读者写者问题.生产者消费者问题是一个著名的线程同步问题,该问题描述如下:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个具有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经放入产品的缓冲区中再次投放产品. 这个生产者消费者题目不仅常用于操

秒杀多线程第四篇 一个经典的多线程同步问题

上一篇<秒杀多线程第三篇原子操作 Interlocked系列函数>中介绍了原子操作在多进程中的作用,如今来个复杂点的.这个问题涉及到线程的同步和相互排斥,是一道很有代表性的多线程同步问题,假设能将这个问题搞清楚,那么对多线程同步也就打下了良好的基础. 程序描写叙述: 主线程启动10个子线程并将表示子线程序号的变量地址作为參数传递给子线程.子线程接收參数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出參数和全局变量. 要求: 1.子线程输

转--秒杀多线程第四篇 一个经典的多线程同步问题

上一篇<秒杀多线程第三篇原子操作 Interlocked系列函数>中介绍了原子操作在多进程中的作用,现在来个复杂点的.这个问题涉及到线程的同步和互斥,是一道非常有代表性的多线程同步问题,如果能将这个问题搞清楚,那么对多线程同步也就打下了良好的基础. 程序描述: 主线程启动10个子线程并将表示子线程序号的变量地址作为参数传递给子线程.子线程接收参数 -> sleep(50) -> 全局变量++ -> sleep(0) -> 输出参数和全局变量. 要求: 1.子线程输出的线

转--- 秒杀多线程第六篇 经典线程同步 事件Event

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇 一个经典的多线程同步问题> <秒杀多线程第五篇 经典线程同步关键段CS> 上一篇中使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步.本篇介绍用事件Event来尝试解决这个线程同步问题. 首先介绍下如何使用事件.事件Event实际上是个内核对象,它的使用非常方便.下面列出一些常用的函数. 第一个 CreateEvent 函数功能:创建事件 函数原型: HANDLEC

转--- 秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数