阅读本篇之前推荐阅读以下姊妹篇:
《秒杀多线程第四篇 一个经典的多线程同步问题》
《秒杀多线程第五篇 经典线程同步关键段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()就可以完成清理与销毁了。
在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。详见代码:
运行结果如下图:
可以看出来,经典线线程同步问题已经圆满的解决了——线程编号的输出没有重复,说明主线程与子线程达到了同步。全局资源的输出是递增的,说明各子线程已经互斥的访问和输出该全局资源。
现在我们知道了如何使用事件,但学习就应该要深入的学习,何况微软给事件还提供了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)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。
代码如下:
对自动置位事件,运行结果如下:
对手动置位事件,运行结果如下:
最后总结下事件Event
1.事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。
2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
3.事件可以解决线程间同步问题,因此也能解决互斥问题。
后面二篇《秒杀多线程第七篇 经典线程同步 互斥量Mutex》和《秒杀多线程第八篇 经典线程同步 信号量Semaphore》将介绍如何使用互斥量和信号量来解决这个经典线程同步问题。欢迎大家继续秒杀多线程之旅。
转载请标明出处,原文地址:http://blog.csdn.net/morewindows/article/details/7445233
如果觉得本文对您有帮助,请点击‘顶’支持一下,您的支持是我写作最大的动力,谢谢。
恩,对头
0x77598E19 (ntdll.dll) (Thread_learning.exe 中)处有未经处理的异常: 0xC0000005: 写入位置 0x00000014 时发生访问冲突。
中断位置在 EnterCriticalSection(&g_csThreadCode);
因为主函数的Sleep时间比Fun函数中的Sleep时间短,SetEvent(EventHandle);会在主函数中连续运行很多次,然后进入任意一个Fun函数后,遇到WaitForSingleObject后EventHandle就被复位了(自动复位),多个等待的Fun函数只有一个运行了。
也就是说这样最后显示出的数据比预期要少
大致顺序是:启动子线程i->主线程sleep,同时子线程开始读取全局变量并进行相应操作并进入等待->主线程醒来,触发事件->子线程开始sleep->主线程开启下一个线程。
后续打印出来的结果顺序就不一定正确了,没法保证打印出来的顺序是同步的,但是可以保证操作的过程是同步的。
大神能否详细说明一下,为什么只换一下Sleep的位置,就会出现这种情况?
大致顺序是:启动子线程i->主线程sleep,同时子线程开始读取全局变量并进行相应操作并进入等待->主线程醒来,触发事件->子线程开始sleep->主线程开启下一个线程。
后续打印出来的结果顺序就不一定正确了,没法保证打印出来的顺序是同步的,但是可以保证操作的过程是同步的。
SetEvent(g_hThreadEvent); //触发事件
把上面的两句代码换一下就会出现错误,?????
SetEvent(g_hThreadEvent); //触发事件
int nThreadNum = *(int *)pPM;
楼主解释一下
你把位置换一下, 主线程没有等子线程取得 i 的值就把 i++ 了.
楼主,其实我觉得你还没有详细回答他的问题,我也有同样的疑问,我觉得这个要从pulseEvent产生的复位脉冲的时间长度上来解释,假设我们已手动复位的事件为前提,如果pulseEvent产生的这个脉冲极短那么任何一个线程都无法被触发,因为置位后会马上复位;如果时间较短,那么可能只够1,2个线程得到复位信号,只有这个脉冲足够长才能够保证5个快线程全部得到复位信号,从楼主的代码运行结构上看,这个脉冲信号是足够长的,足以保证5个快线程在pulseEvent将事件复位前得到事件置位的消息。楼主觉得我说的有道理吗
你理解错了吧
改为TRUE吧
这个不是正常,只是降低了线程相互干扰的概率。这个sleep是为了增加线程相互干扰出现概率,没什么其他用途的。
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楼贴出来的一样。
1.windows下的多线程是真的多线程吗?还是看上去是“并发的”,跟处理的单核,多核是什么个关系啊?忘博主指点..
这是做了一点修改,两者的功能在手动设置和自动设置所实现的功能完全一样!
int nThreadNum = *(int *)pPM;
SetEve...
我感觉博主的意思是说事件可以处理线程同步;如果把fun()函数中的setEvent()放到最后一行,那就彻底的同步了:主线程创建子线程,然后子线程一直执行到最后一行的setEvent();这样一来,主线程与子线程就彻底的同步,就如同只有一个线程在执行;可是这好像又没有多大意义;毕竟博主想让大家明白多线程的学习;
多线程的同步 求关注
拜托你可以谦虚一点吗
int nThreadNum = *(int *)pPM;
SetEvent(g_hThreadEvent); //触发事件
这行代码唯一的作用就是使 线程的标号同步了,如果没有这句的由于传递的是地址,所有标号最后都是9号。
LZ不会为了标号同步使用了一个核心对象吧。。。速度可比CRITICAL慢100倍呢。
int nThreadNum = *(int *)pPM;
SetEvent(g_hThreadEvent); //触发事件
掉后之后线程就变成同步的了?这两个函数就第二行有点作用,请问为什么调用SetEvent()之后就变成你所谓的同步了呢?
http://blog.csdn.net/morewindows/article/details/744263
因此线程同步的常用工具可以说有关键段,事件,互斥量,信号量这四种,但其实关键段与互斥量只能用于互斥,事件与信号量才既能用于同步,也能用于互斥。
你说线程同步的话,那跟函数调用还有什么区别了呢?
所以我觉得线程的根本是,再不涉及到共享数据结构的时候,就是随便执行,什么同步不同步的,只有涉及到了共享数据,然后大家再同步。你觉得呢?
另外,关键代码段,我刚进线程函数就ENTER,难道这不算线程同步吗?
http://blog.csdn.net/morewindows/article/details/744263 中的例子吧。
既然都用关键代码段把全局的变量保护起来了,那么为什么还要用事件呢?
这个程序使用关键代码段也可以实现同步啊。
http://blog.csdn.net/morewindows/article/details/744263
有关键区,
有event ,mutex,semaphore,
其中event和semaphore的线程所有权 性质,有点乱。
现
现在以event为例子,汇总下自己的调查结果,大家看对不对?
汇总如下:
1)关键段有“线程所有权”特性,所以关键段只能用于线程的互斥而不能用于同步。//from morewindows
2)只有持有该event的线程可以释放它,其他线程调用释放没有效果。
3)如果持有该event的线程意外退出,该event将会被遗弃,被系统回收。
另外,我对楼主的代码改动一下,不用临界段,只使用事件对象,并且移动了线程函数的位置。从运行结果上来看,实现了多线程的同步。不知道算不算是逻辑上有问题,楼主的看法呢?
运行结果:
线程编号为0 全局资源值为1
线程编号为1 全局资源值为2
线程编号为2 全局资源值为3
线程编号为3 全局资源值为4
线程编号为4 全局资源值为5
线程编号为5 全局资源值为6
线程编号为6 全局资源值为7
线程编号为7 全局资源值为8
线程编号为8 全局资源值为9
线程编号为9 全局资源值为10
首先说明下我没有学过windows下的多线程编程,学的是Linux下的,但我想原理应该想通吧 。
问题是:在您第一个代码的第24行是以下代码:
WaitForSingleObject(g_hThreadEvent, INFINITE); //等待事件被触发
我们以创建第一个线程时为例,如果第一个线程刚创建时系统就把运行权给了第一个线程,即开始执行Fun函数内的语句。当Fun函数中运行的语句SetEvent运行结束后系统才将控制权转给主线程,即系统开始执行WaitForSingleObject 语句。 也就是说如果SetEvent语句在WaitForSingleObject语句执行之前,那么系统会不会出现阻塞等待?
不知道解释清楚了没,期待您的答疑,谢谢呵呵。
--我尝试回答一下。呵呵
--我跟caigen1988有同样的疑问。
“就是说如果SetEvent语句在WaitForSingleObject语句执行之前,那么系统会不会出现阻塞等待? ”
--我认为:系统会出现阻塞等待,并且一直等待下去,直到遇到下个SetEvent给的信号。如果SetEvent只执行一次,并且在WaitForSingleObject前执行,那么WaitForSingleObject永远得不到触发,所以会一直阻塞下去。
--以上想法,请大家判明。
Event和OVERLAPPED组合
这里的一个或多个是由什么决定的?
这个在我怎么试不出来呀?你能否截个图给我下,谢谢。
[email protected]
这个是因为在一个进程中,如果主线程结束运行会导致整个进程的终止,这样其它线程都会被强行中止。所以在本倒中,主线程要sleep(3000)以保证其它子线程都有机会得到执行。
2.线程被触发是否就是指线程由不可运行状态转换到可运行状态?在这里,WaitForMultipleObjects()函数的作用就是阻塞了主线程,直到其他的对象全部什么了什么了之后才重新唤醒主线程,也就是说这个函数阻塞当前线程,直到由他创建的线程全部执行完之后才被唤醒。
第二个问题,在http://blog.csdn.net/morewindows/article/details/7421759这里有讲解。
1.线程同步和线程互斥在定义上和概念有什么区别呢?
2.在什么场合使用线程同步,在什么场合使用线程互斥呢?
...
经典....顶!