用户模式的线程同步机制效率高,如果需要考虑线程同步问题,应该首先考虑用户模式的线程同步方法。但是,用户模式的线程同步有限制,对于多个进程之间的线程同步,用户模式的线程同步方法无能为力。这时,只能考虑使用内核模式。
Windows提供了许多内核对象来实现线程的同步。对于线程同步而言,这些内核对象有两个非常重要的状态:“已通知”状态,“未通知”状态(也有翻译为:受信状态,未受信状态)。Windows提供了几种内核对象可以处于已通知状态和未通知状态:进程、线程、作业、文件、控制台输入/输出/错误流、事件、等待定时器、信号量、互斥对象。
与事件EVENT的配合使用,能够解决很多同步问题,也可以在数据达到某个状态时启动另一个线程的执行,如报警。
EVENT 的几个函数:
1、CreateEvent和OpenEvent
HANDLE WINAPI CreateEvent(__in LPSECURITY_ATTRIBUTES lpEventAttributes, //表示安全控制,一般直接传入NULL,表示不能被子进程继承__in BOOL bManualReset, //参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。__in BOOL bInitialState, //Event的初始状态, TRUE为触发,FALSE未触发__in LPCTSTR lpName //Event object的名字,NULL表示没名字(without a name)
);
要是CreateEvent创建的事件没名字 这个函数就没啥用了,不多做介绍,可查看msn。
HANDLE WINAPI OpenEvent( //获得已经存在的Event的事件句柄__in DWORD dwDesiredAccess,__in BOOL bInheritHandle,__in LPCTSTR lpName //要打开的事件名字
);
2、SetEvent,触发事件
BOOL SetEvent(HANDLE hEvent);
3、ResetEvent,使事件状态设为未触发,如在创建事件时第二个参数为TRUE手动设置,则需要该函数去恢复事件为未触发状态。
BOOL SetEvent(HANDLE hEvent);
4、PulseEvent, 如在创建事件时第二个参数为TRUE手动设置,其功能相当于SetEvent()后立即调用ResetEvent(),最好别用
BOOL PulseEvent(HANDLE hEvent)
5、CloseHandle(),关闭该句柄。
事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。
WaitForSingleObject()
在多线程下面,有时候我们会希望等待某一线程完成了再继续做其他事情,要实现这个目的,可以使用Windows API函数WaitForSingleObject,或者WaitForMultipleObjects。这两个函数都会等待Object被标为有信号(signaled)时才返回的。
那么,什么是信号呢?
简单来说,Windows下创建的Object都会被赋予一个状态量。如果Object被激活了,或者正在使用,那么该Object就是无信号,也就是不可用;另一方面,如果Object可用了,那么它就恢复有信号了。
这两个函数的优点是它们在等待的过程中会进入一个非常高效沉睡状态,只占用极少的CPU时间片。(这两个函数都是在内核状态下等待内核对象,不切换到用户模式下,因而效率很高)
1、格式
DWORD WaitForSingleObject( HANDLE hHandle, DWORDdwMilliseconds);
有两个参数,分别是THandle和Timeout(毫秒单位)。
如果想要等待一条线程,那么你需要指定线程的Handle,以及相应的Timeout时间。当然,如果你想无限等待下去,Timeout参数可以指定系统常量INFINITE。
WaitForSingleObject函数用来检测hHandle事件的信号状态,当函数的执行时间超过dwMilliseconds就返回,但如果参数dwMilliseconds为INFINITE时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到WaitForSingleObject有返回值才执行后面的代码。此外,当dwMilliseconds设置为特殊值0时,测试hHandle核心对象是否被激发,函数立即返回。
2. 使用对象
它可以等待如下几种类型的对象:
Event(事件),Mutex(互斥量),Semaphore(信号量),Process(进程),Thread(线程),Change notification(变更通知),Console input(控制台输入),Job(可以被理解为进程的容器),Memory resource notification(内存资源通知),Waitable timer(等待定时器)
3. 返回类型
WAIT_ABANDONED:当hHandle为mutex时,如果拥有mutex的线程在结束时没有释放核心对象会引发此返回值。
WAIT_OBJECT_0:核心对象已被激活WAIT_TIMEOUT:等待超时WAIT_FAILED:出现错误,可通过GetLastError得到错误代码
4.示例:
#include <windows.h>
#include <stdio.h>
#include <iostream.h> //声明函数 创建线程
DWORD WINAPI FunProc( LPVOID lpParameter); void main()
{ HANDLE hThread; hThread=CreateThread(NULL,0,FunProc,NULL,0,NULL); DWORD dwRet=WaitForSingleObject(hThread, 1); if(dwRet==WAIT_OBJECT_0) { printf("创建的线程执行结束\n"); } if(dwRet==WAIT_TIMEOUT) { printf("等待超时\n"); } if(dwRet==WAIT_ABANDONED) { printf("Abandoned\n"); } CloseHandle(hThread);
} DWORD WINAPI FunProc( LPVOID lpParameter )
{ int i=1; for(; i<1000; i++) { printf("%d ", i); if(! (i%10)) printf("\n"); } return 0;
}
注意:不可以在WaitForSingleObject()之前执行CloseHandle()否则会导致程序出错!!
官方文档解释:
如果在wait操作仍处于暂挂状态时关闭此句柄,则函数的行为将不明确。
WaitForMultipleObjecct()
WaitForMultipleObjects是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的所有的内核对象
函数原型为:
DWORD WaitForMultipleObjects( DWORD nCount, // number of handles in the handle array CONST HANDLE *lpHandles, // pointer to the object-handle array BOOL fWaitAll, // wait flag DWORD dwMilliseconds // time-out interval in milliseconds );
参数解析:
DWORD
就是Double Word
, 每个word为2个字节的长度,DWORD
双字即为4个字节,每个字节是8位。nCount
指定列表中的句柄数量 最大值为MAXIMUM_WAIT_OBJECTS(64)
*lpHandles
句柄数组的指针。lpHandles
为指定对象句柄组合中的第一个元素 HANDLE类型可以为(Event
,Mutex
,Process
,Thread
,Semaphore
)数组bWaitAll
等待的类型,如果为TRUE
,表示除非对象都发出信号,否则就一直等待下去;如果FALSE
,表示任何对象发出信号即可dwMilliseconds
指定要等候的毫秒数。如设为零,表示立即返回。如指定常数INFINITE
,则可根据实际情况无限等待下去
函数的返回值有:
WAIT_ABANDONED_0
:所有对象都发出消息,而且其中有一个或多个属于互斥体(一旦拥有它们的进程中止,就会发出信号)WAIT_TIMEOUT
:对象保持未发信号的状态,但规定的等待超时时间已经超过WAIT_OBJECT_0
:所有对象都发出信号WAIT_IO_COMPLETION
:(仅适用于WaitForMultipleObjectsEx
)由于一个I/O完成操作已作好准备执行,所以造成了函数的返回- 返回
WAIT_FAILED
则表示函数执行失败,会设置GetLastError
如bWaitAll
为FALSE
,那么返回结果相似,只是可能还会返回相对于WAIT_ABANDONED_0
或WAIT_OBJECT_0
的一个正偏移量,指出哪个对象是被抛弃还是发出信号。
WAIT_OBJECT_0
是微软定义的一个宏,你就把它看成一个数字就可以了。
例如,WAIT_OBJECT_0
+ 5的返回结果意味着列表中的第5个对象发出了信号
如果程序中的nObjectWait
是WAIT_OBJECT_0
+ 5
int nIndex
= nObjectWait
- WAIT_OBJECT_0
;就是说nIndex
=5
也就表示第5
个对象发出了信号
示例:
当 bWaitAll参数为FALSE可以等待其中之一的事件
HANDLE m_hEvent[2]; //两事件 m_hEvent[0]=CreateEvent(NULL, FALSE, FALSE, NULL);
m_hEvent[1]=CreateEvent(NULL, FALSE, FALSE, NULL);
CreateThread(NULL, 0, MyThreadProc, this, 0, NULL);
DWORD WINAPI MyThreadProc(LPVOID lpParam)
{
while(TRUE) { //每次等500毫秒 int nIndex = WaitForMultipleObjects(2, pThis->m_hEvent, FALSE,500); if (nIndex == WAIT_OBJECT_0 + 1) { //第二个事件发生 //ExitThread(0); //break;
} else if (nIndex == WAIT_OBJECT_0) //第一个事件发生
{ //第一个事件 }
else if (nIndex == WAIT_TIMEOUT) //超时500毫秒
{ //超时可作定时用
}
} OutputDebugString("线程结束. /n"); return 0L;}
当要处理第一个事件时,你只需执行SetEvent(m_hEvent[0]); 即可进入第一个事件的位置
当要执行第二个事件时执行SetEvent(m_hEvent[1]);
当 bWaitAll参数为TRUE等待所有的事件
DWORD WINAPI MyThreadProc(LPVOID lpParam)
{ while(TRUE) { //每次等500毫秒
int nIndex = WaitForMultipleObjects(2, pThis->m_hEvent, TRUE,500); if (WAIT_OBJECT_0 + 1<= nIndex <= WAIT_OBJECT_0) //所有事件发生 { //所有的信号量都有效时(事件都发生)其中之一无效。 }