内核态同步对象之“事件”

事件是一种很常使用到的,用于同步的内核对象。

它分为两种:<手动挡的>,<自动挡的>


手动挡:顾名思义,如果一个事件激活之后,你不显式调用ResetEvent的话,那么事件就一直处于激活状态。

自动挡:在任何一个线程waitfor函数成功之后,该事件就会被重新调整为未激活状态。


手动挡的事件通常应用于这么一个场景:

你想象一下十一假期某天清晨的故宫,外面一票人现在等着开门,里面一票人也忙着进行什么调包文物,修建会馆啊等利国利民的大事。(一群线程等待某些线程完成工作)。

等上面这些大事进行完成之后,门开了!(手动挡的事件现在被激活)

伴随着门口那些比如“妈!你在哪?”,“别挤啦!!!”,“我的鞋!!!”等多种多样的喊叫声,人民群众开始有条不紊地进入故宫参观(所有wait该事件的线程现在都结束了wait状态进行各自的工作。)

参观的基本原则就是只许看不许摸。(某游客不满道:“摸一下又不会怀孕的撒~~~”。)(如果涉及到共享数据区,那么这么线程必须都只能以只读的方式操作数据)

在一天的打砸抢,啊,不对,参观结束之后,人民群众纷纷离开了故宫,大门关了(在线程各自工作完成之后,事件被显示的调整为未激活状态,所有这些线程又要重新开始等待。)


自动档的则好说了,这就相当于每次故宫放进去一个游客后就关大门,直到这位游客看爽了之后再把大门打开放下一个。


事件对象在使用上有什么需要注意的呢?

有关于什么死锁什么之类的我就不谈了,在这里我只说这样一个问题:

如果对一个已经激活的对象再调用SetEvent进行激活,该API不返回错误,而且也没有效果


来看一段坑爹的代码:

HANDLE g_hEvent;
unsigned int __stdcall Thread(void* pParam)
{
	while(1)
	{
		WaitForSingleObject(g_hEvent,INFINITE);
		printf_s("hahahaha\n");
	}
	return 0;
}
unsigned int __stdcall Triger(void* pParam)
{
	SetEvent(g_hEvent);
	printf_s("%d\n",GetLastError());
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	g_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
	_beginthreadex(NULL,0,Thread,NULL,0,NULL);
	_beginthreadex(NULL,0,Triger,NULL,0,NULL);
	SetEvent(g_hEvent);
	printf_s("%d\n",GetLastError());
}

你猜,

“hahahaha”

会打印出来几个?


如果说,整个程序的执行是这么一个序列:

1. 线程1进入等待

2. 主线程激活事件

3. 线程1打印后等待

4. 线程2激活事件

5. 线程1打印


那明显结论是两个。

但是吧,你怎么这么保证不是这么一个序列?

1. 线程1等待

2. 主线程激活事件

3. 线程2激活事件

4. 线程1打印


有同学肯定会说了,在激活事件的同时OS可以查看是否有线程正在等待,如果有等待的,则等待线程直接结束等待状态。我说你太天真了。

再看这么一段更坑爹的代码:

HANDLE g_hEvent;
unsigned int __stdcall Thread(void* pParam)
{
	while(1)
	{
		WaitForSingleObject(g_hEvent,INFINITE);
		printf_s("hahahaha\n");
	}
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	g_hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
	_beginthreadex(NULL,0,Thread,NULL,0,NULL);
	SetEvent(g_hEvent);
	SetEvent(g_hEvent);
	printf_s("%d\n",GetLastError());
}
这段代码良好的证明了激活事件后等待的线程不是立刻就调入CPU运行的。不信你就自己运行一下看看。


所以,使用事件的时候最好不要在线程之间交叉进行SetEvent,而且事件的激活应该有明确的目的性以及清晰的逻辑,在两次激活操作之间必须能够明确保证事件会进入到未激活状态。

你可能感兴趣的:(Windows核心编程,null,thread,工作,api,os)