[一个经典的多线程同步问题]解决方案二:Event事件
使用关键段来解决经典的多线程同步互斥问题,由于关键段的“线程所有权”特性所以关键段只能用于线程的互斥而不能用于同步。
本篇介绍用事件Event来尝试解决这个线程同步问题。
首先介绍下如何使用事件。事件Event实际上是个内核对象,它的使用非常方便。下面列出一些常用的函数。
- 第一个 CreateEvent
- 函数功能:创建事件
- 函数原型:
- HANDLECreateEvent(
- LPSECURITY_ATTRIBUTES lpEventAttributes,
- BOOL bManualReset,
- BOOL bInitialState,
- LPCTSTR lpName
- );
- 函数说明:
- 第一个参数表示安全控制,一般直接传入NULL。
- 第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。
- 如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。
- 打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。
- 自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。
- 第三个参数表示事件的初始状态,传入TRUR表示已触发。
- 第四个参数表示事件的名称,传入NULL表示匿名事件。
- 第二个 OpenEvent
- 函数功能:根据名称获得一个事件句柄。
- 函数原型:
- HANDLEOpenEvent(
- DWORDdwDesiredAccess,
- BOOLbInheritHandle,
- LPCTSTRlpName //名称
- );
- 函数说明:
- 第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。
- 第二个参数表示事件句柄继承性,一般传入TRUE即可。
- 第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。
- 第三个SetEvent
- 函数功能:触发事件
- 函数原型:BOOLSetEvent(HANDLEhEvent);
- 函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。
- 第四个ResetEvent
- 函数功能:将事件设为末触发
- 函数原型:BOOLResetEvent(HANDLEhEvent);
- 最后一个事件的清理与销毁
- 由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。
在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。详见代码:
- #include <stdio.h>
- #include <process.h>
- #include <windows.h>
- long g_num;
- const int THREAD_NUM = 10;
- unsigned int __stdcall Fun(void *param);
- HANDLE g_hThreadEvent;
- CRITICAL_SECTION g_csThreadCode;
- int main(void)
- {
- //初始化事件 自动置位,初始化无触发的匿名事件
- g_hThreadEvent = CreateEvent(NULL, false, false, NULL);
- //初始化关键段
- InitializeCriticalSection(&g_csThreadCode);
- HANDLE handle[THREAD_NUM];
- g_num = 0;
- int i = 0;
- while(i < THREAD_NUM)
- {
- handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
- WaitForSingleObject(g_hThreadEvent, INFINITE);//等待事件触发
- i++;
- }
- WaitForMultipleObjects(THREAD_NUM, handle, true, INFINITE);
- //销毁事件和关键段
- CloseHandle(g_hThreadEvent);
- DeleteCriticalSection(&g_csThreadCode);
- return 0;
- }
- unsigned int __stdcall Fun(void *param)
- {
- int nThreadNum = *(int *)param;
- SetEvent(g_hThreadEvent);//触发事件
- Sleep(50);
- EnterCriticalSection(&g_csThreadCode);
- g_num++;
- Sleep(0);
- printf("线程编号为%d 全局资源为%d\n", nThreadNum, g_num);
- LeaveCriticalSection(&g_csThreadCode);
- return 0;
- }
执行结果:
执行的结果说明了各个子线程都正确的取得了它们的id号码。但是之所以输出的编号不是连续的,因为它们的执行速度不一样。这说明了它们正确的和main线程同步,同时各个子线程之间很好的互斥访问了全局的变量。
现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了PulseEvent()函数,所以接下来再继续深挖下事件Event,看看它还有什么秘密没。
先来看看这个函数的原形:
- 第五个PulseEvent
- 函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。
- 函数原型:BOOLPulseEvent(HANDLEhEvent);
- 函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:
- 1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。
- 2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。
- 此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。
下面对这个触发一个事件脉冲PulseEvent ()写一个例子,主线程启动7个子线程,其中有5个线程Sleep(10)后对一事件调用等待函数(称为快线程),另有2个线程Sleep(100)后也对该事件调用等待函数(称为慢线程)。主线程启动所有子线程后再Sleep(50)保证有5个快线程都正处于等待状态中。此时若主线程触发一个事件脉冲,那么对于手动置位事件,这5个线程都将顺利执行下去。对于自动置位事件,这5个线程中会有中一个顺利执行下去。而不论手动置位事件还是自动置位事件,那2个慢线程由于Sleep(100)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。
- //使用PluseEvent()函数
- #include <stdio.h>
- #include <conio.h>
- #include <process.h>
- #include <windows.h>
- HANDLE g_hThreadEvent;
- //快线程
- unsigned int __stdcall FastThreadFun(void *param)
- {
- Sleep(10);
- printf("%s 启动\n", (PSTR)param);
- WaitForSingleObject(g_hThreadEvent, INFINITE);
- printf("%s 等到事件被触发 顺利结束\n", (PSTR)param);
- return 0;
- }
- //慢线程
- unsigned int __stdcall SlowThreadFun(void *param)
- {
- Sleep(100);
- printf("%s 启动\n", (PSTR)param);
- WaitForSingleObject(g_hThreadEvent, INFINITE);
- printf("%s 等到事件被触发 顺利结束\n", (PSTR)param);
- return 0;
- }
- int main(void)
- {
- //每一个线程的名字
- char szFastThreadName[5][20] = {"快线程001","快线程002","快线程003","快线程004","快线程005"};
- char szSlowThreadName[2][20] = {"慢线程001","慢线程002"};
- bool bManualReset = false;
- /*bool bManualReset = true;*/
- //创建事件 第二个参数手动置位true,自动置位false
- g_hThreadEvent = CreateEvent(NULL, bManualReset, false, NULL);
- if (bManualReset == true)
- {
- printf("当前使用手动置位事件\n");
- }
- else
- {
- printf("当前使用自动置位事件\n");
- }
- int i;
- for (i = 0; i < 5; i++)
- {
- _beginthreadex(NULL, 0, FastThreadFun, szFastThreadName[i], 0, NULL);
- }
- for (i = 0; i < 2; i++)
- {
- _beginthreadex(NULL, 0, SlowThreadFun, szSlowThreadName[i], 0, NULL);
- }
- Sleep(50);//保证线程全部启动
- printf("现在主线程触发一个事件脉冲 - PulseEvent()\n");
- PulseEvent(g_hThreadEvent); //调用PulseEvent()就相当于同时调用下面两句话
- //SetEvent(g_hThreadEvent);
- //ResetEvent(g_hThreadEvent);
- Sleep(3000);
- printf("时间到,主线程结束运行\n");
- CloseHandle(g_hThreadEvent);
- return 0;
- }
对于自动置位事件,运行的结果:
对于手动置位事件,运行结果:
最后总结下事件Event
1.事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
3.事件可以解决线程间同步问题,也能解决互斥问题。解决线程互斥时要把事件设置为自动置位,同时初始状态为true,触发模式。这样第一个调用WaitForSingleObject的线程能通过,之后置位为未触发,阻挡后来的线程。
[一个经典的多线程同步问题]解决方案二:Event事件的更多相关文章
- [一个经典的多线程同步问题]解决方案一:关键段CS
前面提出了一个经典的多线程同步互斥问题,本篇将用关键段CRITICAL_SECTION来尝试解决这个问题. 本文先介绍如何使用关键段,然后再深层次的分析下关键段的实现机制和原理. 关键段CRITICA ...
- [一个经典的多线程同步问题]解决方案三:互斥量Mutex
本篇通过互斥量来解决线程的同步,学习其中的一些知识. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互 ...
- C++多线程同步技巧(二)--- 事件
简介 Windows在线程控制方面提供了多种信号处理机制,其中一种便是使用 CreateEvent() 函数创建事件,然后使用信号控制线程运行.其中将事件变为有信号可使用 SetEvent() 函数, ...
- Windows多线程同步系列之二-----关键区
关键区对象为:CRITICAL_SECTION 当某个线程进入关键区之后,其他线程将阻塞等待,知道该线程释放关键区的拥有权. 关键区同步主要有以下几个API 初始化关键区对象,无返回值,传入一个关键区 ...
- 多线程同步内功心法——PV操作上(未完待续。。。)
阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event& ...
- 并发编程--一堆锁,GIL,同步异步,Event事件
目录 一堆锁 死锁现象(*****) 递归锁 RLock (了解) 信号量 (了解) GIL(*****) 什么时GIL锁 为什么需要GIL锁 Cpython解释器与GC的问题 GIL锁带来的问题 多 ...
- Python 多线程、多进程 (二)之 多线程、同步、通信
Python 多线程.多进程 (一)之 源码执行流程.GIL Python 多线程.多进程 (二)之 多线程.同步.通信 Python 多线程.多进程 (三)之 线程进程对比.多线程 一.python ...
- java 轻量级同步volatile关键字简介与可见性有序性与synchronized区别 多线程中篇(十二)
概念 JMM规范解决了线程安全的问题,主要三个方面:原子性.可见性.有序性,借助于synchronized关键字体现,可以有效地保障线程安全(前提是你正确运用) 之前说过,这三个特性并不一定需要全部同 ...
- java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...
随机推荐
- A. Anton and Letters
time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...
- 【转】zookeeper 的监控工具
公司很多产品会使用zookeeper,比如Meta消息中间件,在测试的过程中,我们经常需要查询zookeeper里面的信息来精确定位问题.目前项目中有开发团队自己写的浏览器node-z ...
- 什么是HTTP Keep-Alive呢?
在通过调试工具查看网络请求的时候,通常在response header能看到类似下面这种:Keep-Alive: timeout=10, max=94 .那么Keep-Alive到底是什么呢? HTT ...
- sticker.js贴纸效果
http://stickerjs.cmiscm.com/ <div class="sticker gbtags"></div> <!-- 引用Java ...
- UVA 820 Internet Bandwidth
题意: 给出双向图,求给出两点的流通总流量. 分析: 网络流中的增广路算法. 代码: #include <iostream>#include <cstring>#include ...
- ASP.NET怎么防止多次点击提交按钮重复提交
今天做一个系统,由于服务器有点慢,所以常会被点击两次或三次,提交的数据就是多次了.所以要让按钮点击后,不能再次点击. 对于一个按钮,要让变成恢色的,只要this.disabled=true就可以了,可 ...
- 从客户端中检测到有潜在危险的Request.Form值的解决办法
http://www.pageadmin.net/article/20141016/935.html 如果你网站iis服务器asp.net采用了4.0的版本,则默认会阻止客户端的html内容提交,提交 ...
- bash基础知识
站在用户登录的角度来说,SHELL的类型:登录式shell: 正常通常某终端登录 su - USERNAME su -l USERNAME 非登录式shell: su USERNAME 图形终端下打开 ...
- c++试题
一.写一个函数找一个字符串中出现频率最高的字符(若最高的相同,取先出现的) char finchar(const char *str) { ; } ]; , n = ; ; str[i]!=; i++ ...
- jQuery实现逐字输入效果
之前做了个测试小游戏(姑且叫游戏吧)为了增加神秘性,就想给她加个逐字输入效果:刚好在网上找到一个挺好用的,于是就发扬拿来主义:按照自己的喜好做了一丢丢的修改(勉强算是吧\( ̄︶ ̄)> ). 代码 ...