Event是windows操作系统的一种内核对象,它可以用于进程间同步和线程间同步。
进程间同步的使用
在windows所提供的内核对象中,Event内核对象比其他内核对象,如信号量,互斥量,简单的多。Event只有是否触发的属性。
下面是Event内核对象的函数接口:
(1)CreateEvent() 创建事件
HANDLE WINAPI CreateEvent(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_ BOOL bManualReset,
_In_ BOOL bInitialState,
_In_opt_ LPCTSTR lpName
);
lpEventAttributes: 安全属性,一般置为NULL;
bManualReset: 是手动重置事件(TRUE) ,自动重置事件(FALSE);
bInitialState: 初始状态:触发状态(TRUE)还是非触发状态(FALSE);
lpName: 创建 事件对象的名字,用于进程间的共享;
如果该事件对象已经存在,那么CreateEvent会返回该内核对象的句柄,并通过系统返回错误ERROR_ALREADY_EXISTS,可以通过GetLastError()获得该错误号。
(2)OpenEvent() 打开一个事件内核对象
HANDLE WINAPI OpenEvent(
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ LPCTSTR lpName
);
dwDesiredAccess:指定想要的访问权限,EVENT_ALL_ACCESS请求对事件对象的完全访问,
EVENT_MODIFY_STATE允许使用SetEvent,,ResetEvent和PulseEvent函数;
bInheritHandle:是否希望子进程继承事件对象的句柄,一般设置为false;
lpName:要打开的事件对象的名称;
其他进程中的线程可以通过OpenEvent或CreateEvent访问已经存在的事件内核对象。和其他内核对象的访问一样。
(3)WaitForSingleObject() 等待函数
DWORD WINAPI WaitForSingleObject(
_In_ HANDLE hHandle,
_In_ DWORD dwMilliseconds
);
hHandle:指向内核对象的句柄;
dwMilliseconds:线程最大等待多长时间,直到该对象被触发。经常使用INFINITE,表示阻塞等待。
WaitForSingleObject被称呼为等待函数,是等待内核对象被触发通用的等待函数,它被用在所有的内核对象触发等待中。当事件对象处于未触发状态,等待函数会被阻塞。当处于触发状态时,等待函数会被系统调用,成功返回。当等待函数返回后,该事件对象的状态是被重置为未触发状态还是仍然处于触发状态,由该事件对象是自动重置还是手动重置事件决定。当该事件对象是自动重置事件时,在等待函数返回时,该事件会自动变成未触发状态,如果事件对象为手动重置事件,那么等待函数返回后,该事件仍然处于触发状态,直到调用ResetEvent函数后,才使该事件变成未触发状态。
(4)SetEvent()
BOOL WINAPI SetEvent(
_In_ HANDLE hEvent
);
hObject:指向内核对象的句柄
设置事件内核对象为触发状态;
(5)ResetEvent()
BOOL WINAPI ResetEvent(
_In_ HANDLE hEvent
);
hObject:指向内核对象的句柄
设置事件内核对象为未触发状态;对于事件内核对象,当该事件对象被设置为自动重置事件的时候,ResetEvent的调用时不必要的,因为在自动重置事件上进行等待时,即调用WaitForSingleObject,当等待函数返回时,该事件会被自动重置为未触发的状态。
(6)CloseHandle()
BOOL WINAPI CloseHandle(
_In_ HANDLE hObject
);
hObject:指向内核对象的句柄
和其他内核对象一样,无论以什么方式创建内核对象,我们都必须通过调用CloseHandle向系统表明结束使用内核对象。如果传入的句柄有效,系统将获得内核对象数据结构的地址,并将结构中的使用计数减1,如果使用计数0,就会将内核对象销毁,从内存空间中擦除。
控制进程:
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, "Global\Sync_Signal");
//......某些操作
SetEvent(hEvent);
同步进程:
HANDLE hEvent = OpenEvent(SYNCHRONIZE, FALSE, "Global\Sync_Signal");
if(hEvent != NULL)
cout<<"Event Openned!"<<endl;
else
{
cout<<"Error Openning Event"<<endl;
cout<<"Last Error: "<<GetLastError()<<endl;
cin>>ch;
exit(0);
}
WaitForSingleObject(hEvent, INFINITE); //等待信号量触发
cout<<"Got Event!"<<endl;
//......同步以后的操作
//Close Event Handle
CloseHandle(hEvent);
注意,这里打开事件内核对象的时候,权限只用了SYNCHRONIZE,这是因为同步进程只需要等待触发,而不需要释放资源,或者对事件做处理。当然,如果需要Set或Reset事件,也是可以的,只要增加EVENT_MODIFY_STATE权限即可。
关于内核对象的访问权限,见MSDNhttp://msdn.microsoft.com/zh-cn/library/ms686670(v=vs.85).aspx
The Windows security model enables you to control access to event, mutex, semaphore, and waitable timer objects. Timer queues, interlocked variables, and critical section objects are not securable.
事件做为内核对象,即可用户线程间同步,又可用于进程同步,下面的测试代码用于进程间同步,不清楚的同学请留言。
A进程Demo:
#include "stdafx.h"
#include<windows.h>
#include <iostream>
using namespace std;
DWORD _stdcall ThreadFunc (_In_ LPVOID p)
{
int x=1;
HANDLE pEvent = *((HANDLE*)p);
while(1)
{
WaitForSingleObject(pEvent, INFINITE);
cout <<"我接收到了信号"<< x << endl;
x++;
}
}
int main()
{
//使用Global开头的名字 是为了保证名字是全局的,从而不论是内核还是应用层都可以使用
HANDLE hEvent = CreateEventA(NULL,false,false,"Global\\myEvent");
HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, &hEvent, 0, NULL);
while(1);
return 0;
}
B进程Demo:
#include "stdafx.h"
#include <windows.h>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hEvent = OpenEventA(EVENT_ALL_ACCESS, false, "Global\\myEvent");
if (hEvent)
{
int x=1;
while(1)
{
SetEvent(hEvent);
cout << "信号激活第" << x << "次\n";
x++;
Sleep(2000);
}
}
return 0;
}
测试结果:
当B进程发送一次信号后,A进程会打印一次接收到信号。
而下面两个进程是通过事件内核对象对文件"c:/test.txt"的操作进行同步,
//process 1
int main()
{
//自动触发事件,初始状态为触发
HANDLE stream1Event = CreateEvent(NULL, false, true, (LPCWSTR)"streamEvent");
WaitForSingleObject(stream1Event, INFINITE);
ofstream fileStream1("c:/test.txt", ios_base::app);
for (int i = 0, j = 1; i < 10; ++i)
{
Sleep(1000);
fileStream1<<j;
fileStream1<<' '<<flush;
}
SetEvent(stream1Event);
CloseHandle(stream1Event);
}
//process 2
int main()
{
//自动触发事件,初始状态为触发
HANDLE stream2Event = CreateEvent(NULL, false, true, (LPCWSTR)"streamEvent");
WaitForSingleObject(stream2Event, INFINITE);
ofstream fileStream2("c:/test.txt", ios_base::app);
for (int i = 0, j = 2; i < 10; ++i)
{
Sleep(1000);
fileStream2<<j;
fileStream2<<' '<<flush;
}
SetEvent(stream2Event);
CloseHandle(stream2Event);
}
结果"c:/test.txt"中的内容如下:
2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
windows核心编程---用内核对象进行线程同步
当一个手动复原的事件对象的状态被置为有信号状态时,该对象将一直保持有信号状态,直至明确调用ResetEvent函数将其置为无符号状态。当事件对象被设置为有信号状态时,任何数量的等待线程或者随后等待的线程都会被释放。
当一个自动复原事件对象的状态被设置为有信号状态时,该对象一直保持有信号状态,直至一个单等待线程被释放;系统然后会自动重置对象到无信号状态。
事件1进程(Event1.exe)
#include <iostream>
#include <windows.h>
#include <string>
using namespace std;
#define EventName "eventName"
int main(int argc, char* argv[])
{
HANDLE handle = CreateEvent(NULL,FALSE, FALSE,EventName);
if (handle != NULL)
{
int count = 0;
while (count < 10)
{
WaitForSingleObject(handle,INFINITE);
cout<<"EVENT1有信号了:"<<++count<<endl;
Sleep(2000);
ResetEvent(handle);
}
}
return 0;
}
从程序中此进程中创建了一个名为eventName的事件,从参数中设置可以看出,此事件是手动重置并且初始化的时候为无信号的。
然后是一个while循环,通过WaitForSingleObjet函数等待此事件为有信号,这样才能打印出下面的信息。启动此进程的时候,进程会一直等待。
事件2进程(Event2.exe)
#include <iostream>
#include <windows.h>
#include <string>
using namespace std;
#define EventName "eventName"
int main(int argc, char* argv[])
{
HANDLE handle = OpenEvent(EVENT_ALL_ACCESS,NULL,EventName);
if (handle != NULL)
{
int count = 0;
while (count < 10)
{
SetEvent(handle);
cout<<"EVENT2有信号了:"<<++count<<endl;
Sleep(2000);
}
}
return 0;
}
从程序中可以看出,此进程没有创建新的进程,而是打开一个进程名为eventName的事件,如果没有此名字的事件,则程序会直接退出。
如果此时,我们已经运行Event1.exe,那么此进程会找到这个事件,执行下面的SetEvent函数,这样事件变为有信号状态。
此时,Event1.exe的WaitForSingleObject函数检测到事件为有信号状态,打印信息,同时Event2.exe无条件的打印
Windows提供了许多内核对象来实现线程的同步。对于线程同步而言,这些内核对象有两个非常重要的状态:“已通知”状态,“未通知”状态(也有翻译为:受信状态,未受信状态)。Windows提供了几种内核对象可以处于已通知状态和未通知状态:进程、线程、作业、文件、控制台输入/输出/错误流、事件、等待定时器、信号量、互斥对象。
不同用户的进程间使用Event
不同权限用户的进程间通过事件通信要注意
高权限用户进程CreateEvent 后,低权限用户进程(普通桌面应用程序)去 OpenEvent 事件失败,报错“拒绝访问”,并且获得的事件句柄是NULL。
服务进程权限较高,而普通用户进程权限较低,权限较低的进程去打开权限较高的进程创建的事件的,会报找不到事件的错误。
原因:
在服务程序中创建的内核对象,默认情况下普通用户进程无法打开这个内核对象,每个内核对象都是有访问控制的,而服务中创建的内核对象权限比较高,当LPSECURITY_ATTRIBUTES这个参数传NULL的时候,将使用默认访问控制。普通用户进程自然没有权限访问了,解决方法如下,在服务进程创建事件对象时,指定确定的安全描述符。我们在创建事件时,通过安全属性降低事件的权限。采用降低服务进程所创建的event的权限,然后低权限进程可以open事件。
SECURITY_DESCRIPTOR secutityDesc;
::InitializeSecurityDescriptor(&secutityDesc, SECURITY_DESCRIPTOR_REVISION);
::SetSecurityDescriptorDacl(&secutityDesc, TRUE, NULL, FALSE);
SECURITY_ATTRIBUTES securityAttr;
securityAttr.nLength = sizeof SECURITY_ATTRIBUTES;
securityAttr.bInheritHandle = FALSE;
securityAttr.lpSecurityDescriptor = &secutityDesc;
m_hEvent = ::CreateEvent(&securityAttr, FALSE, FALSE, L"Global\\TiEvent");
//已设置全局 Event
本文详细介绍了Windows操作系统中Event内核对象的使用,包括如何创建、打开、等待、设置和重置事件,以及其在进程间同步中的作用。通过示例代码展示了如何在不同进程中使用Event进行通信,解释了自动重置和手动重置事件的区别,并提到了不同权限用户进程间使用Event的注意事项。
599

被折叠的 条评论
为什么被折叠?



