为了实现线程之间的同步,我们经常使用事件对象来实现,通常事件使用方法是让一个线程执行初始化的工作,初始化完成之后去触发另一个线程执行其他的工作。事件对象的使用需要和等待函数一块来配合使用才能实现线程之间的同步,等待函数的使用使得相应的线程进入等待状态,当事件对象处于有信号状态时,相应的等待状态会变为可调度状态,从而实现事件对象进程间的同步。下来将结合具体的代码来说明其使用过程。
1、创建事件对象,通过使用CreateEvent函数来实现事件的创建,函数返回值为事件句柄。
HANDLE WINAPI CreateEvent(
__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
__in BOOL bManualReset,
__in BOOL bInitialState,
__in_opt LPCSTR lpName)
主要参数说明:
bManualReset用于设事件的类型属性,如果设置为TRUE则代表该事件为手动重置事件,所谓手动重置事件就是在事件被激活后,等待该事件的所有线程都变为可调度状态;如果设置为FALSE则代表该事件为自动重置事件,自动重置事件就是事件被激活后,等待该事件的所有线程中只有一个变为可调度状态。
bInitialState用于设置事件的初始信号状态,如果设置为TRUE则为有信号状态,为FALSE则为无信号状态,这个主要是用于等待函数获得信号。
2、等待函数说明。平常用的等待函数主要有WaitForSingleObject和WaitForMultipleObjects函数来实现,当事件对象为有信号状态的时候,等待函数执行通过。以上俩个函数的区别在于调用线程检查一个内核对象和多个内核对象,在这里主要使用前者进行讲解。
DWORD WINAPI WaitForSingleObject( __in HANDLE hHandle, __in DWORD dwMilliseconds )
主要参数说明:
hHandle代表事件对象句柄;
dwMilliseconds 用于设置最多等待被触发的时间,如果设置为INFINITE则永远等下去,容易造成阻塞,一直等待下去,在实际应用中曾经碰到过类似的bug,在等待函数执行之前进行了PostMessate消息去改变一些界面的状态变化,发现界面的状态一直没发生变化,直到设置了事件信号之后,界面的状态才有了不断的变化,最后发现问题是由于参数设置为INFINIT导致了堵塞,消息队列一直没法处理发来的Post消息,所以此参数一定要慎用。
返回值说明,线程等待的对象被触发后返回WAIT_OBJECT_0;等待超时返回WAIT_TIMEOUT;错误的情况下返回WAIT_FAILED;
3、设置事件对象的信号状态,通过使用SetEvent函数来设置事件对象为有信号状态,从而触发等待函数。
BOOL WINAPI SetEvent(__in HANDLE hEvent ); // hEvent为事件对象句柄
使用ResetEvent函数来设置事件状态为无信号状态
BOOL WINAPI ResetEvent(__in HANDLE hEvent); // hEvent为事件对象句柄
4、代码示例:通过创建三个线程,使用事件对象实现同步过程。
//创建事件对象
HANDLE g_Event = CreateEvent(NULL,FALSE,FALSE,NULL);
//创建线程
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ProcOne,NULL,0,NULL);
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ProcTwo,NULL,0,NULL);
CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ProcThird,NULL,0,NULL);
//设置事件信号
SetEvent(g_Event);
//线程的相应实现函数
void _stdcall ProcOne()
{
WaitForSingleObject(g_Event,INFINITE);
std::cout<<"call ProcOne"<<std::endl;
return;
}
void _stdcall ProcTwo()
{
WaitForSingleObject(g_Event,INFINITE);
std::cout<<"call ProcTwo"<<std::endl;
return;
}
void _stdcall ProcThird()
{
WaitForSingleObject(g_Event,INFINITE);
std::cout<<"call ProcThird"<<std::endl;
return;
}
5、结果分析:通过修改CreateEvent中的bManualReset参数,可以看出自动重置和手动重置的极大区别,选择手动重置,三个线程都唤醒,有相应的输出;设置为自动重置类型,仅有一个线程被唤醒,有相应的输出,但是据MS介绍,任意一个都有可能被唤醒,在调试的过程中感觉使用的策略是FIFO方法。有兴趣的可以多调整一些相关的参数看看相应的结果,例如WaitForSingleObject函数的第二个参数等。
刚刚开始学习,不足之处希望大家指点!