使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步。
本篇介绍用事件Event来尝试解决这个线程同步问题。

首先介绍下如何使用事件。事件Event实际上是个内核对象,它的使用非常方便。下面列出一些常用的函数。

  1. 第一个 CreateEvent
  2. 函数功能:创建事件
  3.  
  4. 函数原型:
  5. HANDLECreateEvent(
  6. LPSECURITY_ATTRIBUTES lpEventAttributes,
  7. BOOL bManualReset,
  8. BOOL bInitialState,
  9. LPCTSTR lpName
  10. );
  11.  
  12. 函数说明:
  13. 第一个参数表示安全控制,一般直接传入NULL
  14. 第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。
  15. 如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
  16. 打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。
  17. 自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。
  18. 第三个参数表示事件的初始状态,传入TRUR表示已触发。
  19. 第四个参数表示事件的名称,传入NULL表示匿名事件。
  1. 第二个 OpenEvent
  2. 函数功能:根据名称获得一个事件句柄。
  3. 函数原型:
  4. HANDLEOpenEvent(
  5. DWORDdwDesiredAccess,
  6. BOOLbInheritHandle,
  7. LPCTSTRlpName //名称
  8. );
  9. 函数说明:
  10. 第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。
  11. 第二个参数表示事件句柄继承性,一般传入TRUE即可。
  12. 第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
  1. 第三个SetEvent
  2. 函数功能:触发事件
  3. 函数原型:BOOLSetEvent(HANDLEhEvent);
  4. 函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
  1. 第四个ResetEvent
  2. 函数功能:将事件设为末触发
  3. 函数原型:BOOLResetEvent(HANDLEhEvent);
  1. 最后一个事件的清理与销毁
  2. 由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。详见代码:

  1. #include <stdio.h>
  2. #include <process.h>
  3. #include <windows.h>
  4.  
  5. long g_num;
  6. const int THREAD_NUM = 10;
  7. unsigned int __stdcall Fun(void *param);
  8.  
  9. HANDLE g_hThreadEvent;
  10. CRITICAL_SECTION g_csThreadCode;
  11.  
  12. int main(void)
  13. {
  14. //初始化事件 自动置位,初始化无触发的匿名事件
  15. g_hThreadEvent = CreateEvent(NULL, false, false, NULL);
  16. //初始化关键段
  17. InitializeCriticalSection(&g_csThreadCode);
  18.  
  19. HANDLE handle[THREAD_NUM];
  20. g_num = 0;
  21. int i = 0;
  22.  
  23. while(i < THREAD_NUM)
  24. {
  25. handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
  26. WaitForSingleObject(g_hThreadEvent, INFINITE);//等待事件触发
  27. i++;
  28. }
  29.  
  30. WaitForMultipleObjects(THREAD_NUM, handle, true, INFINITE);
  31. //销毁事件和关键段
  32. CloseHandle(g_hThreadEvent);
  33. DeleteCriticalSection(&g_csThreadCode);
  34.  
  35. return 0;
  36. }
  37.  
  38. unsigned int __stdcall Fun(void *param)
  39. {
  40. int nThreadNum = *(int *)param;
  41. SetEvent(g_hThreadEvent);//触发事件
  42.  
  43. Sleep(50);
  44.  
  45. EnterCriticalSection(&g_csThreadCode);
  46. g_num++;
  47. Sleep(0);
  48. printf("线程编号为%d 全局资源为%d\n", nThreadNum, g_num);
  49. LeaveCriticalSection(&g_csThreadCode);
  50.  
  51. return 0;
  52. }

执行结果:

执行的结果说明了各个子线程都正确的取得了它们的id号码。但是之所以输出的编号不是连续的,因为它们的执行速度不一样。这说明了它们正确的和main线程同步,同时各个子线程之间很好的互斥访问了全局的变量。


现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了PulseEvent()函数,所以接下来再继续深挖下事件Event,看看它还有什么秘密没。

先来看看这个函数的原形:

  1. 第五个PulseEvent
  2. 函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。
  3. 函数原型:BOOLPulseEvent(HANDLEhEvent);
  4. 函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:
  5. 1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。
  6. 2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。
  7. 此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。

下面对这个触发一个事件脉冲PulseEvent ()写一个例子,主线程启动7个子线程,其中有5个线程Sleep(10)后对一事件调用等待函数(称为快线程),另有2个线程Sleep(100)后也对该事件调用等待函数(称为慢线程)。主线程启动所有子线程后再Sleep(50)保证有5个快线程都正处于等待状态中。此时若主线程触发一个事件脉冲,那么对于手动置位事件,这5个线程都将顺利执行下去。对于自动置位事件,这5个线程中会有中一个顺利执行下去。而不论手动置位事件还是自动置位事件,那2个慢线程由于Sleep(100)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。

  1. //使用PluseEvent()函数
  2. #include <stdio.h>
  3. #include <conio.h>
  4. #include <process.h>
  5. #include <windows.h>
  6.  
  7. HANDLE g_hThreadEvent;
  8. //快线程
  9. unsigned int __stdcall FastThreadFun(void *param)
  10. {
  11. Sleep(10);
  12. printf("%s 启动\n", (PSTR)param);
  13. WaitForSingleObject(g_hThreadEvent, INFINITE);
  14. printf("%s 等到事件被触发 顺利结束\n", (PSTR)param);
  15.  
  16. return 0;
  17. }
  18. //慢线程
  19. unsigned int __stdcall SlowThreadFun(void *param)
  20. {
  21. Sleep(100);
  22. printf("%s 启动\n", (PSTR)param);
  23. WaitForSingleObject(g_hThreadEvent, INFINITE);
  24. printf("%s 等到事件被触发 顺利结束\n", (PSTR)param);
  25.  
  26. return 0;
  27. }
  28.  
  29. int main(void)
  30. {
  31. //每一个线程的名字
  32. char szFastThreadName[5][20] = {"快线程001","快线程002","快线程003","快线程004","快线程005"};
  33. char szSlowThreadName[2][20] = {"慢线程001","慢线程002"};
  34. bool bManualReset = false;
  35. /*bool bManualReset = true;*/
  36.  
  37. //创建事件 第二个参数手动置位true,自动置位false
  38. g_hThreadEvent = CreateEvent(NULL, bManualReset, false, NULL);
  39.  
  40. if (bManualReset == true)
  41. {
  42. printf("当前使用手动置位事件\n");
  43. }
  44. else
  45. {
  46. printf("当前使用自动置位事件\n");
  47. }
  48. int i;
  49. for (i = 0; i < 5; i++)
  50. {
  51. _beginthreadex(NULL, 0, FastThreadFun, szFastThreadName[i], 0, NULL);
  52. }
  53. for (i = 0; i < 2; i++)
  54. {
  55. _beginthreadex(NULL, 0, SlowThreadFun, szSlowThreadName[i], 0, NULL);
  56. }
  57.  
  58. Sleep(50);//保证线程全部启动
  59. printf("现在主线程触发一个事件脉冲 - PulseEvent()\n");
  60. PulseEvent(g_hThreadEvent); //调用PulseEvent()就相当于同时调用下面两句话
  61. //SetEvent(g_hThreadEvent);
  62. //ResetEvent(g_hThreadEvent);
  63.  
  64. Sleep(3000);
  65. printf("时间到,主线程结束运行\n");
  66. CloseHandle(g_hThreadEvent);
  67.  
  68. return 0;
  69. }

对于自动置位事件,运行的结果:

对于手动置位事件,运行结果:

最后总结下事件Event

1.事件是内核对象,事件分为手动置位事件自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。

2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。

3.事件可以解决线程间同步问题,也能解决互斥问题。解决线程互斥时要把事件设置为自动置位,同时初始状态为true,触发模式。这样第一个调用WaitForSingleObject的线程能通过,之后置位为未触发,阻挡后来的线程。

[一个经典的多线程同步问题]解决方案二:Event事件的更多相关文章

  1. [一个经典的多线程同步问题]解决方案一:关键段CS

    前面提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题. 本文先介绍如何使用关键段,然后再深层次的分析下关键段的实现机制和原理. 关键段CRITICA ...

  2. [一个经典的多线程同步问题]解决方案三:互斥量Mutex

    本篇通过互斥量来解决线程的同步,学习其中的一些知识. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互 ...

  3. C++多线程同步技巧(二)--- 事件

    简介 Windows在线程控制方面提供了多种信号处理机制,其中一种便是使用 CreateEvent() 函数创建事件,然后使用信号控制线程运行.其中将事件变为有信号可使用 SetEvent() 函数, ...

  4. Windows多线程同步系列之二-----关键区

    关键区对象为:CRITICAL_SECTION 当某个线程进入关键区之后,其他线程将阻塞等待,知道该线程释放关键区的拥有权. 关键区同步主要有以下几个API 初始化关键区对象,无返回值,传入一个关键区 ...

  5. 多线程同步内功心法——PV操作上(未完待续。。。)

    阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...

  6. 并发编程--一堆锁,GIL,同步异步,Event事件

    目录 一堆锁 死锁现象(*****) 递归锁 RLock (了解) 信号量 (了解) GIL(*****) 什么时GIL锁 为什么需要GIL锁 Cpython解释器与GC的问题 GIL锁带来的问题 多 ...

  7. Python 多线程、多进程 (二)之 多线程、同步、通信

    Python 多线程.多进程 (一)之 源码执行流程.GIL Python 多线程.多进程 (二)之 多线程.同步.通信 Python 多线程.多进程 (三)之 线程进程对比.多线程 一.python ...

  8. java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)

    概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...

  9. java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

    本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...

随机推荐

  1. A. Anton and Letters

    time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...

  2. 【转】zookeeper 的监控工具

            公司很多产品会使用zookeeper,比如Meta消息中间件,在测试的过程中,我们经常需要查询zookeeper里面的信息来精确定位问题.目前项目中有开发团队自己写的浏览器node-z ...

  3. 什么是HTTP Keep-Alive呢?

    在通过调试工具查看网络请求的时候,通常在response header能看到类似下面这种:Keep-Alive: timeout=10, max=94 .那么Keep-Alive到底是什么呢? HTT ...

  4. sticker.js贴纸效果

    http://stickerjs.cmiscm.com/ <div class="sticker gbtags"></div> <!-- 引用Java ...

  5. UVA 820 Internet Bandwidth

    题意: 给出双向图,求给出两点的流通总流量. 分析: 网络流中的增广路算法. 代码: #include <iostream>#include <cstring>#include ...

  6. ASP.NET怎么防止多次点击提交按钮重复提交

    今天做一个系统,由于服务器有点慢,所以常会被点击两次或三次,提交的数据就是多次了.所以要让按钮点击后,不能再次点击. 对于一个按钮,要让变成恢色的,只要this.disabled=true就可以了,可 ...

  7. 从客户端中检测到有潜在危险的Request.Form值的解决办法

    http://www.pageadmin.net/article/20141016/935.html 如果你网站iis服务器asp.net采用了4.0的版本,则默认会阻止客户端的html内容提交,提交 ...

  8. bash基础知识

    站在用户登录的角度来说,SHELL的类型:登录式shell: 正常通常某终端登录 su - USERNAME su -l USERNAME 非登录式shell: su USERNAME 图形终端下打开 ...

  9. c++试题

    一.写一个函数找一个字符串中出现频率最高的字符(若最高的相同,取先出现的) char finchar(const char *str) { ; } ]; , n = ; ; str[i]!=; i++ ...

  10. jQuery实现逐字输入效果

    之前做了个测试小游戏(姑且叫游戏吧)为了增加神秘性,就想给她加个逐字输入效果:刚好在网上找到一个挺好用的,于是就发扬拿来主义:按照自己的喜好做了一丢丢的修改(勉强算是吧\( ̄︶ ̄)> ). 代码 ...