概述

事件是内核对象,用于线程间通信和同步。事件分为无信号状态和有信号状态,无信号状态会阻塞到WaitForSingleObject()函数,直到触发事件(即调用SetEvent)。有信号状态则会忽略事件触发信号,不会阻塞到WaitForSingleObject()函数。

 

相关API

ResetEvent(), WaitForSingleObject(), CreateEvent(),SetEvent()

 

函数原型

WINBASEAPI DWORD WINAPI WaitForSingleObject(_In_ HANDLE hHandle,

_In_ DWORD dwMilliseconds);

功能:等待事件被触发

参数:hHandle:事件句柄

dwMilliseconds:等待时间,一般设为INFINITE,意为永久阻塞,直到事件被触发。

如果设为IGNORE则函数立即返回,忽略信号

 

函数原型

WINBASEAPI  _Ret_maybenull_  HANDLE  WINAPI  CreateEventA(

    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,

    _In_ BOOL bManualReset,

    _In_ BOOL bInitialState,

    _In_opt_ LPCSTR lpName

    );

功能:创建事件

参数:lpEventAttributes:表示安全控制,一般直接传入NULL。

bManualReset:判断事件是否手动切换到无信号状态,为true,则需要手动切换,为false,则会自动切换。当调用SetEvent()触发事件时,WaitForSingleObject()就会返回,如果为手动切换,那么必须调用ResetEvent()使事件变成无信号状态这样下次循环到WaitForSingleObject()函数时就会阻塞,否则WaitForSingleObject()函数会立即返回。果为自动切换,WaitForSingleObject()函数则会自动调用ResetEvent()

bInitialState:表示事件的初始状态,TRUR表示已触发。

lpName:    事件的名称NULL表示匿名事件

返回值:事件的句柄

 

函数原型

ResetEvent()

功能:事件设置成无信号状态

参数:hEvent 事件句柄

 

函数原型

SetEvent  BOOL WINAPI SetEvent(_In_ HANDLE  hEvent);

功能:事件触发

参数:hEvent 事件句柄


源码一

HANDLE g_Event = NULL;
unsigned int __stdcall ChildThread(PVOID pM)
{
	int i = (int)pM;
	char c = (char)(i + 65);
	while (true)
	{
		WaitForSingleObject(g_Event, INFINITE);//等待事件被触发
		printf("%c同学抢到电影票,看电影去了\n", c);
	}

	return 0;
}

unsigned int __stdcall ChildThreadFunc(PVOID pM)
{
	while (true)
	{
		Sleep(1000);
		printf("发送事件 \n");
		
		SetEvent(g_Event);  //触发事件
	}

	return 0;
}

int main()
{
	HANDLE handles[5] = { 0 };
	HANDLE handle = 0;
	g_Event = CreateEvent(NULL, FALSE, FALSE, NULL);

	for (int i = 0; i < 5; i++)
	{
		handles[i] = (HANDLE)_beginthreadex(NULL, 0, ChildThread, (void*)i, 0, NULL);
	}
	handle = (HANDLE)_beginthreadex(NULL, 0, ChildThreadFunc, NULL, 0, NULL);

	for (int i = 0; i < 3; i++)
	{
		WaitForSingleObject(handles[i], -1);
	}
	WaitForSingleObject(handle, -1);
	

	//释放线程句柄
	for (int i = 0; i < 3; i++)
	{
		CloseHandle(handles[i]);
	}
	CloseHandle(handle);

	//删除事件
	CloseHandle(g_Event);

	getchar();
	return 0;
}

结果

程序中共创建了6个线程,其中一个线程会调用SetEvent()触发事件,其他5个线程阻塞等待,当事件触发后等待的五个线程中的一个会解除阻塞,执行程序。每触发一次,只能使一个线程解除阻塞。由于操作系统实现了一定的公平性,使每个线程都能得到事件触发,从而使结果看起来比较合理。


源码二

HANDLE g_Event = NULL;
unsigned int __stdcall ChildThread(PVOID pM)
{
	int i = (int)pM;
	char c = (char)(i + 65);
	while (true)
	{
		Sleep(1000);
		WaitForSingleObject(g_Event, INFINITE);//等待事件被触发
		printf("%c同学抢到电影票,看电影去了\n", c);
		ResetEvent(g_Event);
	}

	return 0;
}

unsigned int __stdcall ChildThreadFunc(PVOID pM)
{
	while (true)
	{
		Sleep(1000);
		printf("发送事件 \n");
		
		SetEvent(g_Event);  //触发事件
	}

	return 0;
}


int main()
{
	HANDLE handles[5] = { 0 };
	HANDLE handle = 0;
	g_Event = CreateEvent(NULL, TRUE, FALSE, NULL);

	for (int i = 0; i < 5; i++)
	{
		handles[i] = (HANDLE)_beginthreadex(NULL, 0, ChildThread, (void*)i, 0, NULL);
	}
	handle = (HANDLE)_beginthreadex(NULL, 0, ChildThreadFunc, NULL, 0, NULL);

	for (int i = 0; i < 3; i++)
	{
		WaitForSingleObject(handles[i], -1);
	}
	WaitForSingleObject(handle, -1);
	

	//释放线程句柄
	for (int i = 0; i < 3; i++)
	{
		CloseHandle(handles[i]);
	}
	CloseHandle(handle);

	//删除事件
	CloseHandle(g_Event);

	getchar();
	return 0;
}

结果


相对于源码一,源码二只修改了极少的地方,在调用CreateEvent时第二个参数由false改成了true。并在子线程中加了ResetEvent(),其实就是设置成了手动切换事件状态。但结果却大不相同。每触发一次事件,阻塞在子线程上的事件会全部解除阻塞,而不是触发一次,解除一个。但结果不是想象中的那样,出现了事件触发一次,3个子线程在运行,另外两个没有运行?

其实事件只有在等待时才会被接收(执行到WaitForSingleObject),在程序中每个子线程运行都需要一定的时间,当事件触发时,其中三个线程执行完毕阻塞到WaitForSingleObject等待事件,其他两个正在执行,还未正常等待,所以这两个线程就无法接受到事件,而且事件也不会保存,这就导致了以上的结果。


Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐