Windows进程同步之事件内核对象(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,就会将内核对象销毁,从内存空间中擦除。
下面是例子:
首先下面是两个进程对头一个文件未经同步的操作:
//process 1 int main() { ofstream fileStream1("c:/test.txt", ios_base::app); for (int i = 0, j = 1; i < 10; ++i) { Sleep(1000); fileStream1<<j; fileStream1<<' '<<flush; } } //process 2 int main() { ofstream fileStream2("c:/test.txt", ios_base::app); for (int i = 0, j = 2; i < 10; ++i) { Sleep(1000); fileStream2<<j; fileStream2<<' '<<flush; } }
结果"c:/test.txt"中的内容如下:
2 2 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 1 1
而下面两个进程是通过事件内核对象对文件"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
Jue 14, 2013 @lab