CreateEvent和WaitForSingleObject的用法
进程或者线程之间的同步,需要使用到的函数:
一个CreateEvent
Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle()
来关闭它,用SetEvent()或PulseEvent()来设置它使其有信号。用ResetEvent()来使其无信号。用WaitForSingleObject()或WaitForMultipleObjects()来等待其变为有信号。
一 设置事件的函数
(1). 原形声明
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL
bManualReset, BOOL bInitialState,LPCSTR lpName);
①
lpEventAttributes:确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。
Windows
NT/2000:lpEventAttributes的结构中的成员为新的事件指定了一个安全符。如果lpEventAttributes是NULL,事件将获得一个默认的安全符。
②
bManualReset:指定将事件对象创建成手动复原还是自动复原。TRUE,使用ResetEvent()手动重置为无信号状态;FALSE,当一个等待线程被释放时,自动重置状态为无信号状态。
③
bInitialState:指定事件对象的初始状态,当TRUE,初始状态为有信号状态;当FALSE,初始状态为无信号状态。
④
lpName:指定事件的对象的名称,名字是对大小写敏感的。如果lpName为NULL,将创建一个无名的事件对象。
如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于
bManualReset和bInitialState已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。
如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。
(2). 返回类型
如果函数调用成功,函数返回事件对象的句柄。如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,而且在GetLastError函数中返回ERROR_ALREADY_EXISTS。
如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。
(3). 说明
①
当事件的对象被置为有信号状态时,任意数量的等待中线程,以及随后开始等待的线程均会被释放。
当"手动复原"的置为有信号状态时,该对象状态将一直保持有信号状态,直至明确调用ResetEvent函数将其置为无符号状态。
当"自动复原"的置为有信号状态时,该对象状态将一直保持有信号状态,直至一个等待线程被释放;系统将自动将此函数置为无符号状态。如果没有等待线程正在等待,事件对象的状态将保持有信号状态。
对自动复位的Event对象,它仅释放第一个等到该事件的thread(如果有),而对于人工复位的Event对象,它释放所有等待的thread
。
②
多个进程可持有同一个事件对象的多个句柄,可以通过使用此对象来实现进程间的同步。
·在CreateEvent函数中,lpEventAttributes参数指定句柄可被继承时,通过CreateProcess函数创建的子进程继承的事件对象句柄。
·一个进程可以在DuplicateHandle函数中指定事件对象句柄,从而获得一个复制的句柄,此句柄可以被其它进程使用。
·一个进程可以在OpenEvent或CreateEvent函数中指定一个名字,从而获得一个有名的事件对象句柄。
使用CloseHandle函数关闭句柄。当进程停止时,系统将自动关闭句柄。当最后一个句柄被关闭后,事件对象将被销毁。
(4). 与其有关的函数
一个Event被创建以后,可以用OpenEvent()API来获得它的Handle,用CloseHandle()
来关闭它,用SetEvent()或PulseEvent()来设置它使其有信号。
用ResetEvent()来使其无信号。用WaitForSingleObject()或WaitForMultipleObjects()来等待其变为有信号。
PulseEvent()是一个比较有意思的使用方法,正如这个API的名字,它使一个Event
对象的状态发生一次脉冲变化,从无信号变成有信号再变成无信号,而整个操作是原子的。
二 等待事件的函数
1.
WaitForSingleObject的用法
(1). 原形声明
DWORD WaitForSingleObject( HANDLE hHandle, DWORD
dwMilliseconds);
① hHandle,是想要等待对对象的句柄,可以是下列句柄:
Change notification ,Console
input ,Event ,Job ,
Memory resource notification
,Mutex ,Process
Semaphore ,Thread ,Waitable
timer
②
dwMilliseconds,就是希望等待的时间(毫秒)。0是立即返回,INFINITE(0xFFFFFFFF
或-1)是无限等待。
(2). 返回类型
直到WaitForSingleObject有返回直才执行其后面的代码。
① WAIT_OBJECT_0:
表示等待的对象有信号(对线程来说,表示其执行结束)
② WAIT_FAILED: 表示对象有信号,但还是不能执行
一般是因为未获取到锁或其他原因
③ WAIT_TIMEOUT:
表示等待指定时间内,对象一直没有信号(对线程来说,线程没执行完)
(3).还有一种用法就是我们可以通过WaitForSingleObject函数来间隔的执行一个线程函数的函数体
UINT CMyDlg::Thread2( LPVOID pParam )
{
while(WaitForSingleObject(g_event,MT_INTERVAL)!=WAIT_OBJECT_0)
{
………………
}
return 0;
}
在这个线程函数中可以可以通过设置MT_INTERVAL来控制这个线程的函数体多久执行一次,当事件为无信号状态时函数体隔MT_INTERVAL执行一次,当设置事件为有信号状态时,线程就执行完毕了(return
0)。
(4). 举例
先创建一个全局事件对象:
CEvent g_event;
在程序中可以通过调用CEvent::SetEvent设置事件为有信号状态。
下面是一个线程函数Thread1()
UINT CMyDlg::Thread1( LPVOID pParam )
{
WaitForSingleObject(g_event,INFINITE);
while(1)
{
…………
}
return 0;
}
在线程函数Thread1中,只有g_event为有信号状态,才执行下面的while循环。
全局变量g_event可以在主函数,或者其它线程中g_event.SetEvent来设置。
2. WaitForMultipleObjects的用法
(1). 原形声明
DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles,
BOOL bWaitAll, DWORD dwMilliseconds);
① nCount,用于指定句柄数组的数量
② lpHandles,用于指定句柄数组的内存地址
③ bWaitAll,如果为TRUE 则等待所有信号量有效才往下执行。FALSE
当有其中一个信号量有效时就向下执行。
④ dwMilliseconds,用于指定等待的超时时间(毫秒),可以是INFINITE
(2). 返回类型
① WAIT_OBJECT_0到WAIT_OBJECT_0 + nCount -
1
如果bWaitAll为TRUE,则返回值表明所有指定对象的状态信号
如果bWaitAll为FALSE,则返回值减去WAIT_OBJECT_0表示等到的有效对象在lpHandles数组中的索引。如果多个对象在此过程中有信号,则返回的只是其中序号最小的那个。
② WAIT_ABANDONED_0至WAIT_ABANDONED_0 + nCount -
1
如果bWaitAll为TRUE,则返回值表明所有对象有了信号,并且其中至少一个是废弃的互斥对象。
如果bWaitAll为FALSE,则返回值减去WAIT_ABANDONED_0表示等到的废弃对象在lpHandles数组中的索引。
③
WAIT_TIMEOUTThe超时间隔已过,由bWaitAll参数指定的条件得不到满足。
(3). 处理所有同时触发的内核对象。
监测多事件有一个问题,如果序号最小的那个对象频繁被触发,那么序号比它大的内核对象将得不到被处理的机会。为了解决这一问题,可以采用双WaitForMultipleObjects检测机制来实现。
见下面的例子(来自网上程序,增加上了部分注释,便于理解):
DWORD WINAPI ThreadProc(LPVOID lpPara)
{
DWORD dwRet = 0;
int nIndex = 0;
while(1)
{
//这里最后的参数是INFINITE,是无限等待
dwRet =
WaitForMultipleObjects(nCount,pHandles,false,INFINITE);
switch(dwRet)
{
case WAIT_TIMEOUT:
break;
case WAIT_FAILED:
return 1;
default:
{
nIndex = dwRet - WAIT_OBJECT_0;
ProcessHanlde(nIndex++); //自己编写函数,处理有信号的对象
//同时继续检测其余的事件
while(nIndex < nCount) //nCount事件对象总数
{
//注意这里最后的参数是0 ,不必等待立即返回就行,因为只是监测最小序号的信号对象之后,是否还有有信号的对象
dwRet = WaitForMultipleObjects(nCount -
nIndex,&pHandles[nIndex],false,0);
switch(dwRet)
{
case WAIT_TIMEOUT:
nIndex = nCount; //退出检测,因为没有被触发的对象了.
break;
case WAIT_FAILED:
return 1;
default:
{
nIndex = dwRet - WAIT_OBJECT_0;
ProcessHanlde(nIndex++); //自己编写函数,处理有信号的对象
}
break;