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

阅读本篇之前推荐阅读以下姊妹篇:

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

《秒杀多线程第五篇 经典线程同步关键段CS

 

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

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

 

第一个 CreateEvent

函数功能:创建事件

函数原型:

HANDLECreateEvent(

 LPSECURITY_ATTRIBUTESlpEventAttributes,

 BOOLbManualReset,

 BOOLbInitialState,

 LPCTSTRlpName

);

函数说明:

第一个参数表示安全控制,一般直接传入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()就可以完成清理与销毁了。

 

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

[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. long g_nNum;  
  5. unsigned int __stdcall Fun(void *pPM);  
  6. const int THREAD_NUM = 10;  
  7. //事件与关键段  
  8. HANDLE  g_hThreadEvent;  
  9. CRITICAL_SECTION g_csThreadCode;  
  10. int main()  
  11. {  
  12.     printf("     经典线程同步 事件Event\n");  
  13.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  14.     //初始化事件和关键段 自动置位,初始无触发的匿名事件  
  15.     g_hThreadEvent = CreateEvent(NULL, FALSE, FALSE, NULL);   
  16.     InitializeCriticalSection(&g_csThreadCode);  
  17.   
  18.     HANDLE  handle[THREAD_NUM];   
  19.     g_nNum = 0;  
  20.     int i = 0;  
  21.     while (i < THREAD_NUM)   
  22.     {  
  23.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);  
  24.         WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件被触发  
  25.         i++;  
  26.     }  
  27.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
  28.   
  29.     //销毁事件和关键段  
  30.     CloseHandle(g_hThreadEvent);  
  31.     DeleteCriticalSection(&g_csThreadCode);  
  32.     return 0;  
  33. }  
  34. unsigned int __stdcall Fun(void *pPM)  
  35. {  
  36.     int nThreadNum = *(int *)pPM;   
  37.     SetEvent(g_hThreadEvent); //触发事件  
  38.       
  39.     Sleep(50);//some work should to do  
  40.       
  41.     EnterCriticalSection(&g_csThreadCode);  
  42.     g_nNum++;  
  43.     Sleep(0);//some work should to do  
  44.     printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);   
  45.     LeaveCriticalSection(&g_csThreadCode);  
  46.     return 0;  
  47. }  

运行结果如下图:

可以看出来,经典线线程同步问题已经圆满的解决了——线程编号的输出没有重复,说明主线程与子线程达到了同步。全局资源的输出是递增的,说明各子线程已经互斥的访问和输出该全局资源。

 

现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了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)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。

代码如下:

[cpp]  view plain copy
  1. //使用PluseEvent()函数  
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. HANDLE  g_hThreadEvent;  
  7. //快线程  
  8. unsigned int __stdcall FastThreadFun(void *pPM)  
  9. {  
  10.     Sleep(10); //用这个来保证各线程调用等待函数的次序有一定的随机性  
  11.     printf("%s 启动\n", (PSTR)pPM);  
  12.     WaitForSingleObject(g_hThreadEvent, INFINITE);  
  13.     printf("%s 等到事件被触发 顺利结束\n", (PSTR)pPM);  
  14.     return 0;  
  15. }  
  16. //慢线程  
  17. unsigned int __stdcall SlowThreadFun(void *pPM)  
  18. {  
  19.     Sleep(100);  
  20.     printf("%s 启动\n", (PSTR)pPM);  
  21.     WaitForSingleObject(g_hThreadEvent, INFINITE);  
  22.     printf("%s 等到事件被触发 顺利结束\n", (PSTR)pPM);  
  23.     return 0;  
  24. }  
  25. int main()  
  26. {  
  27.     printf("  使用PluseEvent()函数\n");  
  28.     printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n\n");  
  29.   
  30.     BOOL bManualReset = FALSE;  
  31.     //创建事件 第二个参数手动置位TRUE,自动置位FALSE  
  32.     g_hThreadEvent = CreateEvent(NULL, bManualReset, FALSE, NULL);  
  33.     if (bManualReset == TRUE)  
  34.         printf("当前使用手动置位事件\n");  
  35.     else  
  36.         printf("当前使用自动置位事件\n");  
  37.   
  38.     char szFastThreadName[5][30] = {"快线程1000""快线程1001""快线程1002""快线程1003""快线程1004"};  
  39.     char szSlowThreadName[2][30] = {"慢线程196""慢线程197"};  
  40.   
  41.     int i;  
  42.     for (i = 0; i < 5; i++)  
  43.         _beginthreadex(NULL, 0, FastThreadFun, szFastThreadName[i], 0, NULL);  
  44.     for (i = 0; i < 2; i++)  
  45.         _beginthreadex(NULL, 0, SlowThreadFun, szSlowThreadName[i], 0, NULL);  
  46.       
  47.     Sleep(50); //保证快线程已经全部启动  
  48.     printf("现在主线程触发一个事件脉冲 - PulseEvent()\n");  
  49.     PulseEvent(g_hThreadEvent);//调用PulseEvent()就相当于同时调用下面二句  
  50.     //SetEvent(g_hThreadEvent);  
  51.     //ResetEvent(g_hThreadEvent);  
  52.       
  53.     Sleep(3000);   
  54.     printf("时间到,主线程结束运行\n");  
  55.     CloseHandle(g_hThreadEvent);  
  56.     return 0;  
  57. }  

自动置位事件,运行结果如下:

手动置位事件,运行结果如下:

 

 

最后总结下事件Event

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

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

3.事件可以解决线程间同步问题,因此也能解决互斥问题。

 

后面二篇《秒杀多线程第七篇 经典线程同步 互斥量Mutex》和《秒杀多线程第八篇 经典线程同步 信号量Semaphore》将介绍如何使用互斥量和信号量来解决这个经典线程同步问题。欢迎大家继续秒杀多线程之旅。

 

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

如果觉得本文对您有帮助,请点击支持一下,您的支持是我写作最大的动力,谢谢。



 

187
2
主题推荐
多线程 线程 semaphore 对象 内核
猜你在找
手把手实现红黑树
CC++2014年7月华为校招机试真题一
C++学习之深入理解虚函数--虚函数表解析
cs硕士妹子找工作经历阿里人搜等互联网
我的2012-分享我的四个项目经验
自己选择的路跪着也要走完
面试经验
KMP算法原理与实现精简
Machine Learning---LMS 算法
【精品课程】三维游戏引擎开发-渲染
【精品课程】iOS开发教程之OC语言
【精品课程】思科认证CCNPv2.0详解第3部分 IPSEC VPN技术
【精品课程】C语言入门教程
【精品课程】J2SE轻松入门第二季
准备好了么? 跳吧             ! 更多职位尽在 CSDN JOB
c#软件工程师
广州广电运通信息科技有限公司
|
6-12K/月
我要跳槽
c#.net软件开发工程师
广州萌元信息科技有限公司
|
6-10K/月
我要跳槽
C++开发工程师
广州华工信息软件有限公司
|
4-10K/月
我要跳槽
.net/c#软件开发员(双休,6.5h/天)
广州萌元信息科技有限公司
|
4-6K/月
我要跳槽
id="ad_frm_0" frameborder="0" scrolling="no" src="http://blog.csdn.net/common/ad.html?t=4&containerId=ad_cen&frmId=ad_frm_0" style="border-width: 0px; overflow: hidden; width: 746px; height: 90px;">
查看评论
60楼  dreamgis 2015-01-23 15:00发表 [回复]
引用“Cuckoo0615”的评论:回复Unix_Architect:"同步"是"...
恩,对头
59楼  一根会发音的木头 2015-01-05 18:28发表 [回复]
谢谢分享
58楼  qq_21884085 2014-10-26 00:22发表 [回复]
57楼  z82100 2014-09-23 09:48发表 [回复]
楼主你好,第一段代码我将线程数改到65之后就vs2013会报错了,这是为什么?
0x77598E19 (ntdll.dll) (Thread_learning.exe 中)处有未经处理的异常: 0xC0000005: 写入位置 0x00000014 时发生访问冲突。
中断位置在 EnterCriticalSection(&g_csThreadCode);
Re:  z82100 2014-09-23 10:39发表 [回复]
回复z82100:是因为最大等待数被设置为64了吗?
56楼  kewencommon 2014-09-09 18:19发表 [回复]
[cpp]  view plain copy
  1. 用事件来解决互斥问题,以下的例子有问题么?lz和看见的各位麻烦回答一下  
  2.   
  3.   
  4. #include "stdafx.h"  
  5. #include   
  6. #include  
  7. #include  
  8.   
  9. const int THREAD_NUM = 10;//子线程数  
  10. int g_nNum;//全局变量  
  11. unsigned int __stdcall Fun(void *pSeq);  
  12. HANDLE EventHandle;//事件句柄  
  13.   
  14. int _tmain(int argc, _TCHAR* argv[])  
  15. {  
  16.     HANDLE handle[THREAD_NUM];  
  17.     int i = 0;  
  18.     g_nNum = 0;  
  19.     EventHandle = CreateEvent(NULL,FALSE,FALSE,NULL);  
  20.   
  21.     while(i
  22.     {  
  23.         handle[i] =(HANDLE)_beginthreadex(NULL,0,Fun,NULL,0,NULL);  
  24.         Sleep(10);  
  25.         i++;  
  26.         SetEvent(EventHandle);  
  27. //      Sleep(0);  
  28.     }  
  29.     system("pause");  
  30.     return 0;  
  31. }  
  32.   
  33. unsigned int __stdcall Fun(void *pSeq)  
  34. {  
  35.     int gThreadCount = g_nNum;  
  36.     g_nNum ++;  
  37.     WaitForSingleObject(EventHandle,INFINITE);  
  38.     Sleep(50);//some work to do  
  39.     printf("全局变量[%d]\n",gThreadCount);  
  40.     return 0;  
  41. }  
Re:  FENGYEJINGXIANG 2014-10-16 15:10发表 [回复]
回复kewencommon:这样写是有问题的
因为主函数的Sleep时间比Fun函数中的Sleep时间短,SetEvent(EventHandle);会在主函数中连续运行很多次,然后进入任意一个Fun函数后,遇到WaitForSingleObject后EventHandle就被复位了(自动复位),多个等待的Fun函数只有一个运行了。
也就是说这样最后显示出的数据比预期要少
Re:  TCSS001 2015-04-02 23:00发表 [回复]
回复FENGYEJINGXIANG:主线程虽然sleep时间短,但是子线程是操作完成以后才开始sleep的啊,也就意味着最终肯定能打印出来。
大致顺序是:启动子线程i->主线程sleep,同时子线程开始读取全局变量并进行相应操作并进入等待->主线程醒来,触发事件->子线程开始sleep->主线程开启下一个线程。
后续打印出来的结果顺序就不一定正确了,没法保证打印出来的顺序是同步的,但是可以保证操作的过程是同步的。
Re:  szszszcw 2015-04-07 14:39发表 [回复]
回复K616358281:亲测代码后,发现把Fun函数中的Sleep函数放到WaitForSingleObject上,则只能输出其中一个线程的printf,其余线程均已阻塞。
大神能否详细说明一下,为什么只换一下Sleep的位置,就会出现这种情况?
Re:  TCSS001 2015-04-02 22:59发表 [回复]
回复FENGYEJINGXIANG:主线程虽然sleep时间短,但是子线程是操作完成以后才开始sleep的啊,也就意味着最终肯定能打印出来。
大致顺序是:启动子线程i->主线程sleep,同时子线程开始读取全局变量并进行相应操作并进入等待->主线程醒来,触发事件->子线程开始sleep->主线程开启下一个线程。
后续打印出来的结果顺序就不一定正确了,没法保证打印出来的顺序是同步的,但是可以保证操作的过程是同步的。
55楼  wwhwayne 2014-08-20 15:24发表 [回复]
不管是自动还是手动事件, 为什么在PulseEvent之前,慢线程不启动,然后在PulseEvent之后,对于手动事件,慢线程没被触发,他难道不在等待状态吗?
54楼  Razor87 2014-07-14 16:44发表 [回复]
这个例子,为什么去掉Sleep函数之后结果就不同了?
53楼  microsoftwin32 2014-05-05 11:35发表 [回复]
你好!!我想让线程函数多次执行 必须在线程函数里做循环码??????
52楼  yanziguilai 2014-04-29 23:38发表 [回复]
int nThreadNum = *(int *)pPM; 
SetEvent(g_hThreadEvent); //触发事件
把上面的两句代码换一下就会出现错误,?????
SetEvent(g_hThreadEvent); //触发事件
int nThreadNum = *(int *)pPM; 
楼主解释一下
Re:  落单的毛毛虫 2014-05-02 14:00发表 [回复]
回复yanziguilai:主线程传入 i 的地址.
你把位置换一下, 主线程没有等子线程取得 i 的值就把 i++ 了.
51楼  chengshinanhai 2014-02-24 16:16发表 [回复]
非常感谢楼主的分享
50楼  along_mail 2014-02-24 14:24发表 [回复]
学习了!
49楼  springontime 2014-01-22 10:30发表 [回复]
引用“MoreWindows”的评论:回复cohn:不是的,手动置位表示等待事件的线程不会去修改事件。而且对自动...
楼主,其实我觉得你还没有详细回答他的问题,我也有同样的疑问,我觉得这个要从pulseEvent产生的复位脉冲的时间长度上来解释,假设我们已手动复位的事件为前提,如果pulseEvent产生的这个脉冲极短那么任何一个线程都无法被触发,因为置位后会马上复位;如果时间较短,那么可能只够1,2个线程得到复位信号,只有这个脉冲足够长才能够保证5个快线程全部得到复位信号,从楼主的代码运行结构上看,这个脉冲信号是足够长的,足以保证5个快线程在pulseEvent将事件复位前得到事件置位的消息。楼主觉得我说的有道理吗
Re:  TCSS001 2015-04-02 23:06发表 [回复]
回复springontime:pulseEvent从长短解释不现实,感觉应该是对于自动复位的,所有线程一起抢,抢到了就顺手复位了,可以理解成进教室的例子。而对于手动复位的,不存在抢的问题,就是在那等信号就OK了,有信号来了就往下走,不考虑关门的问题。
48楼  springontime 2014-01-22 10:10发表 [回复]
引用“liuyunjay66”的评论:回复christiana10247:自动复位的话,某一个线程得到触发后,马...
你理解错了吧
47楼  liuphahaha 2014-01-09 17:12发表 [回复]
想请问下博主,为什么输出的线程编号是无序的呢?按道理,对于这句: int nThreadNum = *(int *)pPM; 线程应该是串行的,那么输出应该就是有序的呀?
Re:  MoreWindows 2014-01-09 21:54发表 [回复]
回复liuphahaha:线程创建处理后也不是马上就执行,要等系统的调度。所以有可能后创建处理的线程先执行。
Re:  liuphahaha 2014-01-10 08:50发表 [回复]
回复MoreWindows:哦~~~了解了。。。
46楼  lsk413 2013-12-04 13:27发表 [回复]
单单互斥是子线程之间的,而同步就是主线程与子线程之间以及子线程之间都要互斥,我是这么理解的
45楼  温州的咸菜 2013-11-29 21:48发表 [回复]
学习了
44楼  fly_sky010 2013-11-14 10:49发表 [回复]
我在自己的电脑上测试楼主的代码后是乱序输出,将SetEvent(g_hThreadEvent); //触发事件 放在Sleep(50)后就可以按照正确的顺序输出,我是这样想的,在Sleep(50)之前触发事件,线程会有50ms的等待,其他线程可能在这个时间已经进入临界区了,修改了全局变量的值,所以会乱序。
Re:  MoreWindows 2013-11-16 11:02发表 [回复]
回复fly_sky010:Sleep(50)只是为了方便重现线程冲突,如果不叫仍然存在线程同步问题,只是问题重现的概率比较低。请看《秒杀多线程第四篇 一个经典的多线程同步问题》。
43楼  haithink 2013-09-17 22:42发表 [回复]
"传入TRUR表示已触发"
改为TRUE吧
42楼  d30256 2013-09-05 16:20发表 [回复]
博主,请教一下,我的电脑使用上面的代码,线程代码还是会乱序,如果把sleep(50)去掉就正常了,这是sleep不够准确造成的?还是因为多核cpu导致的?编译环境是vc6.0
Re:  MoreWindows 2013-09-08 13:59发表 [回复]
回复d30256:"如果把sleep(50)去掉就正常了"
这个不是正常,只是降低了线程相互干扰的概率。这个sleep是为了增加线程相互干扰出现概率,没什么其他用途的。
41楼  SEESEECN 2013-08-26 14:08发表 [回复]
线程编号没有递增。改成WaitForsingleObject(handle[i]);是不是更好理解些。
Re:  czl090909 2013-09-06 10:52发表 [回复]
回复SEESEECN:使用WaitForSingleObject()会失去多线程并发的情况,主线程只会在子线程结束之后才会开始下一个线程
40楼  绝杀fc小飞侠 2013-08-21 22:24发表 [回复]
与31楼类似,改变SetEvent(g_hThreadEvent);这句代码的位置,将其放在Fun函数最后,其他不变,即:
unsigned int __stdcall Fun(void *pPM) 

int nThreadNum = *(int *)pPM; 

Sleep(50); 

EnterCriticalSection(&g_csThreadCode); 
g_nNum++; 
Sleep(0); 
printf("线程编号为%d 全局资源值为%d\n",nThreadNum,g_nNum); 
LeaveCriticalSection(&g_csThreadCode); 

SetEvent(g_hThreadEvent); //触发事件 

return 0; 


这样的话,有区别么???菜鸟勿喷。貌似结果跟31楼贴出来的一样。
Re:  拾贝壳的小顽童 2013-08-27 09:31发表 [回复]
回复allen_fan_11:虽然按你的改法nThreadNum也是递增输出的,但这样改的话子线程就相当于串行执行,没有体现出多线程的优越性,也就是说sleep函数的时间cpu仅仅是等待而不是转而执行其他线程。个人愚见~我也是刚看这位大神的博客~
39楼  lynnbest 2013-08-16 16:25发表 [回复]
有几个疑问:
1.windows下的多线程是真的多线程吗?还是看上去是“并发的”,跟处理的单核,多核是什么个关系啊?忘博主指点..
Re:  loving_you2000 2014-09-04 15:29发表 [回复]
回复liuyunjay66:多线程肯定是真的多线程,哪有假的多线程?但是并行可以是真的也可以是“看起来”的,单核CPU的并行肯定是“看起来”的,多核CPU如果把不同的线程放到不同的核里去执行,那就是真的并行。
38楼  xiaohutushen30 2013-07-30 17:00发表 [回复]
[cpp]  view plain copy
  1. int main()  
  2. {  
  3.     while (i < THREAD_NUM)   
  4.     {  
  5.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);  
  6.         i++;  
  7.     }  
  8.     handle[i] = (HANDLE)_beginthreadex(NULL, 0 ,pulsEventFun ,NULL, 0,NULL);  
  9.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
  10.   
  11.     //销毁事件和关键段  
  12.     CloseHandle(g_hThreadEvent);  
  13.     DeleteCriticalSection(&g_csThreadCode);  
  14.     return 0;  
  15. }  
  16. unsigned int __stdcall Fun(void *pPM)  
  17. {  
  18.     int nThreadNum = *(int *)pPM;   
  19.     //SetEvent(g_hThreadEvent); //触发事件  
  20.     WaitForSingleObject(g_hThreadEvent, INFINITE);  
  21.     Sleep(50);//some work should to do  
  22.     EnterCriticalSection(&g_csThreadCode);  
  23.     g_nNum++;  
  24.     Sleep(0);//some work should to do  
  25.     printf("线程编号为%d  全局资源值为%d\n", nThreadNum, g_nNum);   
  26.     LeaveCriticalSection(&g_csThreadCode);  
  27.     return 0;  
  28. }  
  29. unsigned int __stdcall pulsEventFun(void *pPM)  
  30. {  
  31.     while(1)  
  32.     {  
  33.         Sleep(2000);  
  34.         SetEvent(g_hThreadEvent);   //此处1  
  35.         //PulseEvent(g_hThreadEvent);   //此处2  
  36.     }  
  37.     return 0;  
  38. }  

这是做了一点修改,两者的功能在手动设置和自动设置所实现的功能完全一样!
37楼  christiana10247 2013-07-25 10:59发表 [回复]
我有一个地方不太清楚,就是为什么在自动配置的时候,只显示一个事件被触发顺利结束?是因为其他事件还没等到被触发,就CloseHandle了么
Re:  lynnbest 2013-08-16 16:16发表 [回复]
回复christiana10247:自动复位的话,某一个线程得到触发后,马上就调用WaitForSingleObject()。之后 该事件就被复位为未触发了,其他还没有来得及触发的线程,就只有继续等待了..
36楼  ff1429303507 2013-06-21 10:50发表 [回复]
写的不错
35楼  亚细亚 2013-06-08 10:14发表 [回复]
引用“Unix_Architect”的评论:经过测试。
int nThreadNum = *(int *)pPM; 
SetEve...
我感觉博主的意思是说事件可以处理线程同步;如果把fun()函数中的setEvent()放到最后一行,那就彻底的同步了:主线程创建子线程,然后子线程一直执行到最后一行的setEvent();这样一来,主线程与子线程就彻底的同步,就如同只有一个线程在执行;可是这好像又没有多大意义;毕竟博主想让大家明白多线程的学习;
Re:  zyz913614263 2013-06-14 11:32发表 [回复]
回复yaxiya:将setEvent 放到后面。这样就实现了线程号和资源号的对应,但是这样会不会降低同步的效率
Re:  亚细亚 2013-06-14 15:19发表 [回复]
回复zyz913614263:这样好像就不是多线程并发了,应该降低了效率;
34楼  bbc9527 2013-06-06 14:31发表 [回复]
http://bbs.csdn.net/topics/390482498
多线程的同步 求关注
33楼  liulefirst 2013-05-09 10:24发表 [回复]
不知道怎么回事,我用VS2008重现不了线程之间的同步,晕了。
32楼  jianxianbai 2013-04-17 14:36发表 [回复]
引用“Unix_Architect”的评论:回复MoreWindows:我就是一直不理解,线程互斥了的效果和线程同步不...
拜托你可以谦虚一点吗
31楼  其实我很菜 2013-04-02 22:34发表 [回复]
你好,问个问题:多个线程等待同一个事件时,他们是排队等待的么?
Re:  超然_烟火 2013-04-09 19:22发表 [回复]
回复yuanyuanzhouyuan:应该是根据线程切换随机的
30楼  woshinia 2013-01-21 13:48发表 [回复]
事件确实可以严格控制每个线程的执行顺序,但大多数时候,每个线程都是一个单独的任务副本,即不存在说一定要先让线程A操作公共资源再让线程B操作,B先A后也可以,只要保证公共资源不被同时改写,能正确的读写就行了。因此大多数时候临界区就可以了,事件的开销要大很多。
Re:  MoreWindows 2013-01-22 20:36发表 [回复]
回复woshinia:这个是的,用户级线程同步互斥对象和内核级线程同步对象所消费的资源相差还是有点大。
Re:  woshinia 2013-01-21 14:16发表 [回复]
回复woshinia:事件和信号量一般在线程间通信,即有数据交互的时候才会用。
Re:  sunshinewave 2013-03-31 13:22发表 [回复]
楼主只是说了怎样使用事件。但对于什么情况下最适合用事件介绍的太少,望楼主能添加一些。
29楼  Unix_Architect 2013-01-01 11:04发表 [回复]
经过测试。
int nThreadNum = *(int *)pPM; 
SetEvent(g_hThreadEvent); //触发事件 
这行代码唯一的作用就是使 线程的标号同步了,如果没有这句的由于传递的是地址,所有标号最后都是9号。

LZ不会为了标号同步使用了一个核心对象吧。。。速度可比CRITICAL慢100倍呢。
Re:  Unix_Architect 2013-01-01 14:45发表 [回复]
回复Unix_Architect:另外一个疑问是。
int nThreadNum = *(int *)pPM; 
SetEvent(g_hThreadEvent); //触发事件 
掉后之后线程就变成同步的了?这两个函数就第二行有点作用,请问为什么调用SetEvent()之后就变成你所谓的同步了呢?
Re:  MoreWindows 2013-01-01 14:06发表 [回复]
回复Unix_Architect:关键段有“线程所有权”概念的,不能用于线程同步,请看《秒杀多线程第五篇 经典线程同步 关键段CS .》
http://blog.csdn.net/morewindows/article/details/744263
Re:  Unix_Architect 2013-01-01 14:43发表 [回复]
回复MoreWindows:线程所不所有权不知道,但是CRITICAL确实实现了变量之间的互斥效果。为什么书上把CRITICAL列入了同步机制的范畴呢。
Re:  MoreWindows 2013-01-01 14:56发表 [回复]
回复Unix_Architect:呵呵,从广义上讲,线程互斥也可以认为是线程同步的一种,这个在《计算机操作系统》(汤子赢)书上有解释。
因此线程同步的常用工具可以说有关键段,事件,互斥量,信号量这四种,但其实关键段与互斥量只能用于互斥,事件与信号量才既能用于同步,也能用于互斥。
Re:  Unix_Architect 2013-01-01 15:14发表 [回复]
回复MoreWindows:又开始从广义上讲了?刚才是从微观上讲的?
你说线程同步的话,那跟函数调用还有什么区别了呢?
所以我觉得线程的根本是,再不涉及到共享数据结构的时候,就是随便执行,什么同步不同步的,只有涉及到了共享数据,然后大家再同步。你觉得呢?

另外,关键代码段,我刚进线程函数就ENTER,难道这不算线程同步吗?
Re:  MoreWindows 2013-01-01 16:15发表 [回复]
回复Unix_Architect:你看下《秒杀多线程第五篇 经典线程同步 关键段CS .》
http://blog.csdn.net/morewindows/article/details/744263 中的例子吧。
Re:  Unix_Architect 2013-01-01 17:01发表 [回复]
回复MoreWindows:哎呀,百度写错了,你赶紧纠正一下啊。百度说用critical section全局对象来保持线程同步啊。你赶紧给百度打个电话。
Re:  MoreWindows 2013-01-01 17:41发表 [回复]
回复Unix_Architect:我有这个必要吗?
Re:  Unix_Architect 2013-01-01 16:53发表 [回复]
回复MoreWindows:我就是一直不理解,线程互斥了的效果和线程同步不是一个效果吗?
Re:  CUCKOO0615 2013-03-19 16:17发表 [回复]
回复Unix_Architect:"同步"是"互斥"的同时还有对顺序的要求,是更严格的"互斥",它们的区别都不知道还敢跑来乱说话??
28楼  Unix_Architect 2013-01-01 10:45发表 [回复]
While the thread is sitting waiting for the event, a device driver or part of the kernel itself might ask to borrow the thread to do some processing (by means of a "kernel-mode APC"). During that time, the thread is not in the wait state. (It's being used by the device driver.) If the PulseEvent happens while the thread is being "borrowed", then it will not be woken from the wait, because the PulseEvent function wakes only threads that were waiting at the time the PulseEvent occurs.
27楼  Unix_Architect 2013-01-01 10:43发表 [回复]
LZ请教一下。
既然都用关键代码段把全局的变量保护起来了,那么为什么还要用事件呢?

这个程序使用关键代码段也可以实现同步啊。
Re:  MoreWindows 2013-01-01 14:00发表 [回复]
回复Unix_Architect:关键段有“线程所有权”概念的,请看《秒杀多线程第五篇 经典线程同步 关键段CS .》
http://blog.csdn.net/morewindows/article/details/744263
26楼  titer1 2012-09-15 19:22发表 [回复]
最近看了 windows系列的同步变量。
有关键区,
有event ,mutex,semaphore,
其中event和semaphore的线程所有权 性质,有点乱。


现在以event为例子,汇总下自己的调查结果,大家看对不对?


汇总如下:
1)关键段有“线程所有权”特性,所以关键段只能用于线程的互斥而不能用于同步。//from morewindows

2)只有持有该event的线程可以释放它,其他线程调用释放没有效果。
3)如果持有该event的线程意外退出,该event将会被遗弃,被系统回收。
25楼  Aselan 2012-07-15 20:57发表 [回复]
这样写好像是等到每一个线程差不多快要退出了才触发,没有太大意义啊……
Re:  MoreWindows 2012-07-16 09:32发表 [回复]
回复Aselan:对的,你这样做已经相当于for循环了。失去了多线程并发的优势了。
24楼  Aselan 2012-07-15 20:50发表 [回复]
楼主写得很好,让我对多线程的理解更加深刻了。
另外,我对楼主的代码改动一下,不用临界段,只使用事件对象,并且移动了线程函数的位置。从运行结果上来看,实现了多线程的同步。不知道算不算是逻辑上有问题,楼主的看法呢?
[cpp]  view plain copy
  1. unsigned int __stdcall Fun(void *pPM)  
  2. {  
  3.     int nThreadNum = *(int *)pPM;  
  4.   
  5.     Sleep(50);  
  6.   
  7.     //EnterCriticalSection(&g_csThreadCode);  
  8.     g_nNum++;  
  9.     Sleep(0);  
  10.     printf("线程编号为%d 全局资源值为%d\n",nThreadNum,g_nNum);  
  11.     //LeaveCriticalSection(&g_csThreadCode);  
  12.     SetEvent(g_hThreadEvent);   //触发事件  
  13.     return 0;  
  14. }  


运行结果:
线程编号为0 全局资源值为1
线程编号为1 全局资源值为2
线程编号为2 全局资源值为3
线程编号为3 全局资源值为4
线程编号为4 全局资源值为5
线程编号为5 全局资源值为6
线程编号为6 全局资源值为7
线程编号为7 全局资源值为8
线程编号为8 全局资源值为9
线程编号为9 全局资源值为10
Re:  zhuyf87 2013-06-15 16:02发表 [回复]
回复Aselan:这已经不是多线程并发了,而是多个线程依次运行。每一个新线程开启,都要等上一个线程完事。
Re:  jianxianbai 2013-04-17 14:39发表 [回复]
回复Aselan:你这样的话,每个线程都是等到之前的线程运行结束才开始下一个线程的运行,这个时候多线程就没有意义了
Re:  小强_加油 2012-08-15 21:39发表 [回复]
回复Aselan:这样的话,不用事件也可以出现这种状况的吧
Re:  小强_加油 2012-08-15 21:37发表 [回复]
回复Aselan:哥们,你这样改的话,执行过程就是创建一个线程后。等待这个线程执行结束。才创建下一个,所以才出现这种情况,是吗?
Re:  Aselan 2012-08-19 20:17发表 [回复]
是的。正如楼主所说的一样,这样丧失了多线程并发的优势。要发挥多线程并发的优势,应该只在需要同步的地方进行同步才行。回复aini201:
Re:  MoreWindows 2012-08-16 10:37发表 [回复]
回复aini201:他这样做,每个子线程要等前一个子线程执行完毕才能启动,失去了多线程并发的优势了。
23楼  caigen1988 2012-07-13 17:03发表 [回复]
楼主,我有个问题想请教下您。
首先说明下我没有学过windows下的多线程编程,学的是Linux下的,但我想原理应该想通吧 。
问题是:在您第一个代码的第24行是以下代码:
WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件被触发 
我们以创建第一个线程时为例,如果第一个线程刚创建时系统就把运行权给了第一个线程,即开始执行Fun函数内的语句。当Fun函数中运行的语句SetEvent运行结束后系统才将控制权转给主线程,即系统开始执行WaitForSingleObject 语句。 也就是说如果SetEvent语句在WaitForSingleObject语句执行之前,那么系统会不会出现阻塞等待? 
不知道解释清楚了没,期待您的答疑,谢谢呵呵。
Re:  MoreWindows 2012-10-24 14:10发表 [回复]
回复caigen1988:“如果第一个线程刚创建时系统就把运行权给了第一个线程”,这个在Windows下不是这样的,在Windows下,父线程在创建子线程后,父子线程会同时执行,二个线程都有运行权的。
Re:  excelbeginner 2012-08-25 17:17发表 [回复]
回复caigen1988:“不知道解释清楚了没,期待您的答疑,谢谢呵呵。”
--我尝试回答一下。呵呵
--我跟caigen1988有同样的疑问。
“就是说如果SetEvent语句在WaitForSingleObject语句执行之前,那么系统会不会出现阻塞等待? ”
--我认为:系统会出现阻塞等待,并且一直等待下去,直到遇到下个SetEvent给的信号。如果SetEvent只执行一次,并且在WaitForSingleObject前执行,那么WaitForSingleObject永远得不到触发,所以会一直阻塞下去。
--以上想法,请大家判明。
Re:  linrulei11 2012-10-24 11:16发表 [回复]
回复excelbeginner:你们这么说是不对的,只要执行了SetEvent(g_hThreadEvent),事件就会把触发了,然后,WaitForSingleObject(g_hThreadEvent, INFINITE)就会立刻返回。不会出现无穷等的情况
Re:  MoreWindows 2012-10-24 14:12发表 [回复]
回复linrulei11:正解,WaitForSingleObject(g_hThreadEvent, INFINITE)只判断g_hThreadEvent是否触发,未触发就等待,已经触发就直接返回。
Re:  loving_you2000 2014-09-04 15:14发表 [回复]
回复MoreWindows:貌似时间有点长了,不过看到好的讨论,还是想回复一下。我没有专门研究过Linux的线程,但是我是在Windows下做Qt的,在Qt里面,如果不是调用系统的API而是调用Qt的库函数,类似这个WaitForSingleObject(g_hThreadEvent, INFINITE)的机制,如果在等待之前g_hThreadEvent已经被置位了,它是不知道的,会一直等下去,但是Windows原生的不会一直等下去。 Qt里面的很多机制就是Linux的机制,所以我猜这也是Linux和Windows的不同。
22楼  Galaxy_Li 2012-06-22 13:48发表 [回复]
问一下,第五篇中的旋转锁不适用于单CPU,这里单CPU是指单核,还是也包括只有一个CPU多核的情况?
Re:  MoreWindows 2012-07-16 09:30发表 [回复]
回复Galaxy_Li:应该是单核。
21楼  端木昭昭 2012-06-15 10:23发表 [回复]
谢谢MoreWindows,学习了很多。问个问题:“此时若主线程触发一个事件脉冲,那么对于手动置位事件,这5个线程都将顺利执行下去。”我不太理解这件事,时间脉冲不就是一次吗SetEvent(g_hThreadEvent); +ResetEvent(g_hThreadEvent); 在调用SetEevent函数之后应该也只能有一个线程被执行吧,不论它是手动置位还是自动置位?
Re:  MoreWindows 2012-06-15 15:06发表 [回复]
回复cohn:不是的,手动置位表示等待事件的线程不会去修改事件。而且对自动置位,线程会将事件重设为未触发,这样其它等待的线程就只能继续等待。可以将手动置位看成是公交车,自动置位看成的士,公交车一到站(SetEvent),所以在等的人都能上车,然后公交开走(ResetEvent)。而的士一次只上一个,上车的这个人会主动将车门关上(ResetEvent)。
Re:  亚细亚 2013-06-04 12:36发表 [回复]
回复MoreWindows:学习了!
20楼  cargod 2012-05-30 17:40发表 [回复]
目前正在读一个以前程序员写的代码
Event和OVERLAPPED组合
19楼  ren_teng 2012-05-29 16:37发表 [回复]
“每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。”
这里的一个或多个是由什么决定的?
Re:  MoreWindows 2012-05-29 17:38发表 [回复]
回复ren_teng:取决于事件是自动置位还是手动置位。
18楼  caochuanlin1983 2012-05-22 22:44发表 [回复]
你好:请问在PulseEvent()的例子中,主线程为什么要sleep(3000)?在不sleep(3000)的情况下,为什么慢线程也可以等到事件被触发呢?
Re:  MoreWindows 2012-05-23 12:51发表 [回复]
回复caochuanlin1983:“在不sleep(3000)的情况下,为什么慢线程也可以等到事件被触发呢?”
这个在我怎么试不出来呀?你能否截个图给我下,谢谢。
[email protected]
Re:  MoreWindows 2012-05-23 12:47发表 [回复]
回复caochuanlin1983:主线程为什么要sleep(3000)?
这个是因为在一个进程中,如果主线程结束运行会导致整个进程的终止,这样其它线程都会被强行中止。所以在本倒中,主线程要sleep(3000)以保证其它子线程都有机会得到执行。
17楼  linteng2003 2012-05-17 10:18发表 [回复]
呵呵,还真的没有使用过PulseEvent(),还没有遇到这种需求罢。不过确实写的挺好的。
Re:  MoreWindows 2012-05-17 18:35发表 [回复]
回复goodhongwei:PulseEvent()函数对学习和理解事件Event很有帮助,你有空可以编程试下。
16楼  zzggangge 2012-05-16 16:09发表 [回复]
windows 核心编程全部都有
15楼  xiaomi163 2012-05-12 13:00发表 [回复]
希望楼主,继续更新好帖,好方便我们新手学习
14楼  夏夜de星星 2012-05-05 10:43发表 [回复]
唉,讲得比我们老师还好~ 要是有这样水平和耐心的老师就好了!
Re:  MoreWindows 2012-05-05 15:58发表 [回复]
回复chence19871:呵呵,老师们要忙于发EI和SCI了。
13楼  zhaoabc139 2012-05-01 12:34发表 [回复]
必须要感谢下lz,我们在学OS,但是限于课时,只能老师在黑板上讲,大家都听的云里雾里,五一节突然来了道用API解决生产者与消费者的问题……所以到您这来重头学起……
Re:  MoreWindows 2012-05-02 12:31发表 [回复]
回复zhaoabc139:本系列后面会有专门的一篇来讲解生产者与消费者,欢迎继续参阅。
Re:  zhaoabc139 2012-05-02 22:03发表 [回复]
回复MoreWindows:好的,继续关注~
12楼  ZY-Han 2012-04-24 17:57发表 [回复]
good !
11楼  hnzjjwu123 2012-04-14 07:47发表 [回复]
写的不错,学会不少!请教:一次性关闭多个线程内核对象该怎么做?
10楼  好人吗 2012-04-11 22:50发表 [回复]
请教两个小白问题。1.线程有一个入口函数,若是在入口函数中有引用了另个函数,那这个函数是不是也要考虑到线程同步互斥呢?
2.线程被触发是否就是指线程由不可运行状态转换到可运行状态?在这里,WaitForMultipleObjects()函数的作用就是阻塞了主线程,直到其他的对象全部什么了什么了之后才重新唤醒主线程,也就是说这个函数阻塞当前线程,直到由他创建的线程全部执行完之后才被唤醒。
Re:  MoreWindows 2012-04-12 19:35发表 [回复]
回复down_login:第一个问题,线程同步问题要具体情况具体分析。你说的要引用了别一个函数,那要看看这个函数的功能是什么。
第二个问题,在http://blog.csdn.net/morewindows/article/details/7421759这里有讲解。
9楼  matianyu00 2012-04-11 21:51发表 [回复]
楼主你好。看了你的帖子真是学到了很多东西阿。这里我想问几个问题。
1.线程同步和线程互斥在定义上和概念有什么区别呢?
2.在什么场合使用线程同步,在什么场合使用线程互斥呢?
Re:  MoreWindows 2012-04-12 19:33发表 [回复]
回复matianyu00:个人理解:互斥主要指多个线程不能同时访问一个资源,如打印机就是互斥资源。同步是指多个线程要按一定的次序访问,如上餐馆吃饭,只有菜上桌后你才能吃。互斥其实是一种特殊的同步。详细的定义可以参考操作系统方面的书籍。
8楼  aaahuanian 2012-04-11 14:56发表 [回复]
学习
7楼  夜影如歌 2012-04-11 13:19发表 [回复]
Sleep()到底做了什么?主线程sleep了,子线程也跟着sleep么?
Re:  MoreWindows 2012-04-11 14:59发表 [回复]
回复scorpiuseol:调用Sleep()就相当于执行态变成阻塞态,通俗的说就是线程休眠一段时间。线程在执行上肯定是独立的,一个线程调用Sleep()不会影响其它线程。
6楼  夜影如歌 2012-04-11 13:17发表 [回复]
用Mutex可以处理主线程与子线程的同步问题么?
Re:  MoreWindows 2012-04-11 14:55发表 [回复]
回复scorpiuseol:《秒杀多线程第七篇 经典线程同步 互斥量Mutex》会讲解的。
Re:  夜影如歌 2012-04-11 23:43发表 [回复]
回复MoreWindows:继续期待..!~
5楼  夜影如歌 2012-04-11 13:16发表 [回复]
在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥.
...
经典....顶!
Re:  MoreWindows 2012-04-11 14:57发表 [回复]
回复scorpiuseol:经典多线程问题的提出主要是为了让大家熟悉多线程同步互斥问题的“招式”。因此不要局限于一种解法。你可以试下用事件来解决互斥问题。
Re:  夜影如歌 2012-04-11 23:42发表 [回复]
回复MoreWindows:嗯,多谢指点...继续学习~!
4楼  wenke311 2012-04-11 13:11发表 [回复]
简单明了,无需思考
3楼  jianshiku 2012-04-11 10:12发表 [回复]
谢谢楼主分享。
Re:  MoreWindows 2012-04-11 10:38发表 [回复]
回复jianshiku:谢谢支持
2楼  strongcurrent 2012-04-11 09:33发表 [回复]
简单明了,谢谢LZ,收藏了。
Re:  MoreWindows 2012-04-11 10:37发表 [回复]
回复strongcurrent:谢谢支持
1楼  zlevel 2012-04-11 09:15发表 [回复] [引用] [举报]
以前也用过事件,不过PulseEvent()函数还真没用过,先收藏了。
Re:  MoreWindows 2012-04-11 10:38发表 [回复]
回复zlevel:学下PulseEvent()函数对理解事件很有帮助。

你可能感兴趣的:(多线程学习)