windows核心编程-事件内核对象

在所有的内核对象中,事件内核对象是个最基本的对象。事件能够通知一个操作已经完成。

事件内核对象的组成

一个使用计数(与所有内核对象一样),

一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,

一个用于指明该事件处于已通知状态还是未通知状态的布尔值。

 

有两种不同类型的事件对象

一种人工重置的事件另一种自动重置的事件

当人工重置的事件得到通知时等待该事件的所有线程变为可调度线程。

当自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。

 

当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最多。事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它就将事件设置为已通知状态。这时,一直在等待该事件的另一个线程发现该事件已经得到通知,因此它就变成可调度线程。这第二个线程知道第一个线程已经完成了它的操作。

 

用CreateEvent函数创建事件内核对象

HANDLE CreateEvent(

   PSECURITY_ATTRIBUTES psa,

   BOOL fManualReset,

   BOOL fInitialState,

   PCTSTR pszName

);

 

内核对象的操作技巧见前面相关章节,比如,如何设置它们的安全性,如何进行使用计数如何继承它们的句柄如何按名字共享对象等

fMannualReset参数是个布尔值,它能够告诉系统是创建一个人工重置的事件(TRUE)还是创建一个自动重置的事件(FALSE)。

fInitialState参数用于指明该事件是要初始化为已通知状态(TRUE)还是未通知状态(FALSE)。当系统创建事件对象后, createEvent就将与进程相关的句柄返回给事件对象。

 

其他进程中的线程可以获得对该对象的访问权,方法是使用在pszName参数中传递的相同值(见:命名对象),使用继承性使用DuplicateHandle函数等来调用CreateEvent,或者调用OpenEvent ,在pszName参数中设定一个与调用CreateEvent时设定的名字相匹配的名字:

HANDLE OpenEvent(

   DWORD fdwAccess,

   BOOL fInherit,

   PCTSTR pszName

);

与所有情况中一样,当不再需要事件内核对象时,应该调用CloseHandle函数。

 

一旦事件已经创建,就可以直接控制它的状态

当调用SetEvent时,可以将事件改为已通知状态

BOOL SetEvent(HANDLE hEvent);

 

当调用ResetEvent函数时,可以将该事件改为未通知状态

BOOL ResetEvent(HANDLE hEvent);

 

事件成功等待的副作用

自动重置的事件自动重置到未通知状态(所以通常没有必要为自动重置的事件调用ResetEvent函数)。所以他的名字才有自动两字。

人工重置的事件: Microsoft没有人工重置的事件定义成功等待的副作用。所以它才叫人工。

 

简单例子,如何使用事件内核对象对线程进行同步

这个例子清楚地展示出使用人工重置事件自动重置事件之间的差别。

// Create a global handle to a manual-reset, nonsignaled event.

HANDLE g_hEvent;

 

int WINAPI WinMain(...)

{

   // Create the manual-reset, nonsignaled event.

   g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);    //人工

 

   // Spawn 3 new threads.

   HANDLE hThread[3];

   DWORD dwThreadID;

   hThread[0] = _beginthreadex(NULL, 0, WordCount, NULL, 0, &dwThreadID);

   hThread[1] = _beginthreadex(NULL, 0, SpellCheck, NULL, 0, &dwThreadID);

   hThread[2] = _beginthreadex(NULL, 0, GrammarCheck, NULL, 0, &dwThreadID);

 

   OpenFileAndReadContentsIntoMemory(...);

 

   //Allow all 3 threads to access the memory.

   SetEvent(g_hEvent);              // 将事件设置已通知状态

   ...

}

 

DWORD WINAPI WordCount(PVOID pvParam)

{

   //Wait until the file's data is in memory.

   WaitForSingleObject(g_hEvent, INFINITE);

 

   //Access the memory block.

   ...

   return(0);

}

 

DWORD WINAPI SpellCheck(PVOID pvParam)

{

   //Wait until the file's data is in memory.

   WaitForSingleObject(g_hEvent, INFINITE);

 

   //Access the memory block.

   ...

   return(0);

}

 

DWORD WINAPI GrammarCheck(PVOID pvParam)

{

   //Wait until the file's data is in memory.

   WaitForSingleObject(g_hEvent, INFINITE);

 

   //Access the memory block.

   ...

   return(0);

}

当这个进程启动时,它创建一个人工重置的未通知状态的事件,并且将句柄保存在一个全局变量中。这使得该进程中的其他线程能够非常容易地访问同一个事件对象。现在3个线程已经产生。这些线程要等待文件的内容读入内存,然后每个线程都要访问它的数据。一个线程进行单词计数,另一个线程运行拼写检查器,第三个线程运行语法检查器。这3个线程函数的代码的开始部分都相同,每个函数都调用WaitForSingleObject,这将使线程暂停运行,直到文件的内容由主线程读入内存为止。

一旦主线程将数据准备好,它就调用SetEvent,给事件发出通知信号。这时,系统就使所有这3个辅助线程进入可调度状态,它们都获得了CPU时间,并且可以访问内存块。注意,3个线程都以只读方式访问内存。这就是所有3个线程能够同时运行的唯一原因还要注意,如果计算机上配有多个CPU,那么所有3个线程都能够真正地同时运行,从而可以在很短的时间内完成大量的操作。

 

如果这里使用自动重置的事件而不是人工重置的事件,那么应用程序的行为特性就有很大的差别。当主线程调用SetEvent之后,系统只允许一个辅助线程变成可调度状态。同样,也无法保证系统将使哪个线程变为可调度状态。其余两个辅助线程将继续等待。

已经变为可调度状态的线程拥有对内存块的独占访问权。让我们重新编写线程的函数,使得每个函数在返回前调用SetEvent函数(就像WinMain函数所做的那样)。这些线程函数现在变成下面的形式:

DWORD WINAPI WordCount(PVOID pvParam)

{

   //Wait until the file's data is in memory.

   WaitForSingleObject(g_hEvent, INFINITE);

 

   //Access the memory block.

   ...

   SetEvent(g_hEvent);

   return(0);

}

 

DWORD WINAPI SpellCheck(PVOID pvParam)

{

   //Wait until the file's data is in memory.

   WaitForSingleObject(g_hEvent, INFINITE);

 

   //Access the memory block.

   ...

   SetEvent(g_hEvent);

   return(0);

}

 

DWORD WINAPI GrammarCheck(PVOID pvParam)

{

   //Wait until the file's data is in memory.

   WaitForSingleObject(g_hEvent, INFINITE);

 

   //Access the memory block.

   ...

   SetEvent(g_hEvent);

   return(0);

}

当线程完成它对数据的专门传递时,它就调用SetEvent函数,该函数允许系统使得两个正在等待的线程中的一个成为可调度线程。同样,我们不知道系统将选择哪个线程作为可调度线程,但是该线程将进行它自己的对内存块的专门传递。当该线程完成操作时,它也将调用SetEvent函数,使第三个即最后一个线程进行它自己的对内存块的传递。注意,当使用自动重置事件时,如果每个辅助线程均以读/写方式访问内存块,那么就不会产生任何问题,这些线程将不再被要求将数据视为只读数据

 

BOOL PulseEvent(HANDLE hEvent);

PulseEvent函数使得事件变为已通知状态,然后立即又变为未通知状态,这就像在调用SetEvent后又立即调用ResetEvent函数一样。如果在人工重置的事件上调用PulseEvent函数,那么在发出该事件时,等待该事件的任何一个线程或所有线程将变为可调度线程。如果在自动重置事件上调用PulseEvent函数,那么只有一个等待该事件的线程变为可调度线程。如果在发出事件时没有任何线程在等待该事件,那么将不起任何作用。

PulseEvent函数并不非常有用(可以先不管它)。

 


 下面给一个例子:

两个线程:一个线程先执行,写入一个字符串到内存里面。

另一个线程等第一个线程执行完之后执行,将另一个字符串写入到字符串末尾

[cpp]  view plain  copy
  1. #include <iostream>  
  2. #include <windows.h>  
  3. using namespace std;  
  4.   
  5. HANDLE handle_event;  
  6.   
  7. char sz[100];  
  8.   
  9. DWORD WINAPI ThreadWrite1(LPVOID lpParameter)  
  10. {  
  11.     strcpy(sz , "This is my first Thread !");  
  12.     SetEvent(handle_event);  
  13.     return 0;  
  14. }  
  15.   
  16. DWORD WINAPI ThreadWrite2(LPVOID lpParameter)  
  17. {  
  18.     WaitForSingleObject( handle_event , INFINITE );  
  19.     strcat( sz , "111111" );  
  20.     return 0;  
  21. }  
  22.   
  23. int main(int argc, char* argv[])  
  24. {  
  25.   
  26.     handle_event = CreateEvent(NULL , TRUE , FALSE , NULL );  
  27.     HANDLE han[2];  
  28.     han[0] = CreateThread( NULL , 0 , ThreadWrite1 , NULL , 0,NULL );  
  29.     han[1] = CreateThread( NULL , 0 , ThreadWrite2 , NULL , 0,NULL );  
  30.   
  31.     WaitForMultipleObjects( 2 , han , true , INFINITE );  
  32.     CloseHandle(han[0]);  
  33.     CloseHandle(han[1]);  
  34.     CloseHandle(handle_event);  
  35.     cout<<sz<<endl;  
  36.     return 0;  
  37. }  


 

 

 

 

 

成功显示 ,OK!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值