经典线程同步 事件Event

Event 内核对象,实际上是解决线程同步问题的利器。

介绍下函数应用:

 

第一个 CreateEvent

函数功能:创建事件

函数原型:

HANDLECreateEvent(

 LPSECURITY_ATTRIBUTES lpEventAttributes,

 BOOL bManualReset,

 BOOL bInitialState,

 LPCTSTR lpName

);

函数说明:

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

第二个参数确定事件是手动置位还是自动置位,传入TRUE表示手动置位,传入FALSE表示自动置位。

如果为自动置位,则对该事件调用WaitForSingleObject()后会自动调用ResetEvent()使事件变成未触发状态。打个小小比方,手动置位事件相当于教室门,教室门一旦打开(被触发),所以有人都可以进入直到老师去关上教室门(事件变成未触发)。自动置位事件就相当于医院里拍X光的房间门,门打开后只能进入一个人,这个人进去后会将门关上,其它人不能进入除非门重新被打开(事件重新被触发)。

如果是手动置位,则需要自己调用ResetEvent()使事件变成未触发状态。

第三个参数表示事件的初始状态,传入TRUR表示已触发。

第四个参数表示事件的名称,传入NULL表示匿名事件。

 

第二个 OpenEvent

函数功能:根据名称获得一个事件句柄。

函数原型:

HANDLEOpenEvent(

 DWORD dwDesiredAccess,

 BOOL bInheritHandle,

 LPCTSTR lpName     //名称

);

函数说明:

第一个参数表示访问权限,对事件一般传入EVENT_ALL_ACCESS。详细解释可以查看MSDN文档。

第二个参数表示事件句柄继承性,一般传入TRUE即可。

第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个事件。

 

第三个SetEvent

函数功能:触发事件

函数原型:BOOL SetEvent(HANDLE hEvent);

函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。

 

第四个ResetEvent

函数功能:将事件设为末触发

函数原型:BOOL ResetEvent(HANDLE hEvent);

 

最后一个事件的清理与销毁

由于事件是内核对象,因此使用CloseHandle()就可以完成清理与销毁了。

 

在经典多线程问题中设置一个事件和一个关键段。用事件处理主线程与子线程的同步,用关键段来处理各子线程间的互斥。代码如下:

#include <windows.h>
#include <process.h>
#include <iostream>

using namespace std;

const int THREADNUM = 20;
volatile long number = 0;
HANDLE th_event;

CRITICAL_SECTION threadPM,threadCode;

unsigned int __stdcall threadFunc(PVOID pM) {
	int nThreadNum = * ((int *)pM);
	SetEvent(th_event);
	Sleep(100);
	EnterCriticalSection(&threadCode);
	cout << nThreadNum << endl;
	number++;
	Sleep(0);
	LeaveCriticalSection(&threadCode);
	return 0;
}

int main() {
	int num = 20;
	HANDLE handle[THREADNUM];
	InitializeCriticalSection(&threadCode);
	number = 0;
	th_event = CreateEvent(NULL, TRUE, FALSE, NULL);
	for(int i=0; i< THREADNUM; i++) {
		handle[i] = (HANDLE)_beginthreadex(NULL, 0, threadFunc, (PVOID) &i, 0, NULL);
		WaitForSingleObject(th_event, INFINITE);
		ResetEvent(th_event);
	}
	
	WaitForMultipleObjects(THREADNUM, handle, TRUE ,INFINITE); //安全数量为64
	Sleep(500);
	cout << "计数个数为" << number << endl;
	
	CloseHandle(th_event);
	DeleteCriticalSection(&threadCode);
	getchar();
	return 0;
}

 

上述代码是事件对象的手动置位,

下面的两行尤为重要:


保证主线程和子线程之间可以同步访问。

运行效果如下:

经典线程同步 事件Event
 

如果在手动条件下,忘记对事件重新置位,即忘记写resetevent函数,则同步将没有效果,运行如下:

经典线程同步 事件Event
 

在自动置位的条件下,则不用书写resetEvent函数,将TRUE改为FALSE即可。


运行效果如下:

经典线程同步 事件Event
正常。

 

 

接下来还有一个函数值得注意:

第五个PulseEvent

函数功能:将事件触发后立即将事件设置为未触发,相当于触发一个事件脉冲。

函数原型:BOOL PulseEvent(HANDLE hEvent);

函数说明:这是一个不常用的事件函数,此函数相当于SetEvent()后立即调用ResetEvent();此时情况可以分为两种:

 

1.对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。

2.对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。

 

此后事件是末触发的。该函数不稳定,因为无法预知在调用PulseEvent ()时哪些线程正处于等待状态。

 

 

手动置位:代码如下

主线程,启动三个快线程和两个慢线程。

在慢线程创建好以后,故意停顿50毫秒,目的是为了让快线程全部进入对于事件的等待状态,对于事件脉冲有一个充分的感知。

主线程启动所有子线程后再Sleep(50)保证有3个快线程都正处于等待状态中。此时若主线程触发一个事件脉冲,那么对于手动置位事件,这3个线程都将顺利执行下去。对于自动置位事件,这3个线程中会有中一个顺利执行下去。而不论手动置位事件还是自动置位事件,那2个慢线程由于Sleep(100)所以会错过事件脉冲,因此慢线程都会进入等待状态而无法顺利执行下去。

#include <iostream>
#include <windows.h>
#include <process.h>

using namespace std;

HANDLE th_event;

unsigned int __stdcall faseThreadFunc(PVOID pm) {
	char* s = (char *) pm;
	cout << "进入快线程......" << endl;
	Sleep(10);
	WaitForSingleObject(th_event, INFINITE);
	cout << s << endl;
	return 0;
}

unsigned int __stdcall slowThreadFunc(PVOID pm) {
	char* s = (char *) pm;
	cout << "进入慢线程......" << endl;
	Sleep(100);
	WaitForSingleObject(th_event, INFINITE);
	cout << s << endl;
	return 0;
}

int main() {

	bool ev_Manual = true;

	if(ev_Manual) cout << "手动测试状态" << endl;
	else cout << "自动测试状态" << endl;

	HANDLE th_handle1[3];
	HANDLE th_handle2[2];
	char s1[3][10] = {"快线程1", "快线程2", "快线程3"};
	char s2[2][10] = {"慢线程1", "慢线程2"};
	int i;

	th_event = CreateEvent(NULL, ev_Manual, FALSE, NULL);

	for(i=0; i<3; i++) {
		th_handle1[i] = (HANDLE) _beginthreadex(NULL, 0, faseThreadFunc, (PVOID) s1[i], 0, NULL);
	}

	for(i=0; i<2; i++) {
		th_handle2[i] = (HANDLE) _beginthreadex(NULL, 0, slowThreadFunc, (PVOID) s2[i], 0, NULL);
	}

	Sleep(50);
	cout << "现在主线程触发一个事件脉冲" << endl;
	PulseEvent(th_event);

	getchar();
	return 0;
}

运行结果如下:

经典线程同步 事件Event
 

自动置位:

只是将ev_Manual 改为 false就好;

运行结果如下:

经典线程同步 事件Event
 

最后总结下事件Event

1.事件是内核对象,事件分为手动置位事件和自动置位事件。事件Event内部它包含一个使用计数(所有内核对象都有),一个布尔值表示是手动置位事件还是自动置位事件,另一个布尔值用来表示事件有无触发。

2.事件可以由SetEvent()来触发,由ResetEvent()来设成未触发。还可以由PulseEvent()来发出一个事件脉冲。

3.事件可以解决线程间同步问题,因此也能解决互斥问题。

 

 

你可能感兴趣的:(event)