在自动重置事件对象中,当WaitSingleObject/WaitForMultipleObjects接收到SetEvent发送过来的信号后则返回WAIT_OBJECT_0,此时操作系统(待定)自动重置等待的事件对象(即自动将其设置为无信号状态。无论何时通过SetEvent发送过来的信号,只要未被接收到均不会被自动重置。但在未被接收之前可以调用ResetEvent手动重置等待的事件对象,此时等待的事件对象为无信号状态)。在手动重置事件对象中,当WaitSingleObject/WaitForMultipleObjects接收到SetEvent发送过来的信号后则返回WAIT_OBJECT_0,此时需要调用ResetEvent手动重置等待的事件对象(即手动将其设置为无信号状态)。
- #include <windows.h>
- #include <iostream>
- using namespace std;
- DWORD WINAPI ThreadProc(LPVOID lpParam);
- DWORD WINAPI ThreadProc2(LPVOID lpParam);
- DWORD g_dwThreadID;
- DWORD g_dwThreadID2;
- UINT g_nTickets = 300;
- HANDLE g_hEvent = NULL;
- int main(int argc, char* argv[])
- {
- cout << "Main thread is running." << endl;
- HANDLE hHandle = CreateThread(NULL, 0, ThreadProc, NULL, 0, &g_dwThreadID);
- HANDLE hHandle2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, &g_dwThreadID2);
- CloseHandle(hHandle);
- CloseHandle(hHandle2);
- g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
- Sleep(4000);
- system("pause");
- return 0;
- }
- DWORD WINAPI ThreadProc(LPVOID lpParam)
- {
- // cout << "No." << g_dwThreadID << " thread is running." << endl;
- while (TRUE)
- {
- WaitForSingleObject(g_hEvent, INFINITE);
- if (g_nTickets > 0)
- {
- Sleep(1);
- cout << "No.1-" << g_dwThreadID << " sell ticket : " << g_nTickets << endl;
- g_nTickets--;
- SetEvent(g_hEvent);
- }
- else
- {
- break;
- }
- }
- return 0;
- }
- DWORD WINAPI ThreadProc2(LPVOID lpParam)
- {
- // cout << "No." << g_dwThreadID2 << " thread is running." << endl;
- while (TRUE)
- {
- WaitForSingleObject(g_hEvent, INFINITE);
- if (g_nTickets > 0)
- {
- Sleep(1);
- cout << "No.2-" << g_dwThreadID2 << " sell ticket : " << g_nTickets << endl;
- g_nTickets--;
- SetEvent(g_hEvent);
- }
- else
- {
- break;
- }
- }
- return 0;
- }
说明:建议先下载本文配套工程,其中
EventMain工程、EventSubA工程,EventSubB工程分别用于演示进程间通信的主进程和两个子进程
下载地址:http://download.csdn.net/detail/danny_share/7720043
注意:
1.不要F5直接运行
2.编译生成debug目录或者release目录以后,如果要实验第二部分生命周期的时候,请手动打开EventMain.exe和EventSubA.exe;如果要实验第三部分的时候,请只手动打开EventMain.exe,另外两个exe文件不要手动打开
一. 概念
事件实际属于线程同步对象的范畴,主要通过事件状态的改变实现发送通知。
事件属于windows内核对象,其标识符为一个HANDLE句柄,且因为每个进程至少含有一个主线程,因此可用于多进程环境。
二.生命周期
1.出生
通过CreateEvent或者CreateEventEx创建一个事件对象
CreateEvent创建方式
- HANDLE WINAPI CreateEvent(
- _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
- _In_ BOOL bManualReset,
- _In_ BOOL bInitialState,//TRUE表示创建时已是激活状态
- _In_opt_ LPCTSTR lpName
- );
CreateEventEx创建方式
- HANDLE WINAPI CreateEventEx(
- _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
- _In_opt_ LPCTSTR lpName,
- _In_ DWORD dwFlags, //CREATE_EVENT_INITIAL_SET或者CREATE_EVENT_MANUAL_RESET
- _In_ DWORD dwDesiredAccess
- );
(1) lpEventAttributes表示创建时的安全属性
vista系统和win7系统相比于xp,安全性较高,因此,对于大部分将此值设为NULL的应用,可能会发生在xp下运行正常,而在vista和win7异常的情况。
(2)CreateEvent的bManualReset相当于_CreateEventEx的In_DWORD dwFlags
如果为manual,每次事件激活以后,需调用ResetEvent设置非激活
若为auto,则时间处于激活状态后,只要有一个wait成功,就会自动处于非激活状态
(3)CreateEventEx比CreateEvent少了一个初始状态参数bInitialState,通过以下实验可以证明,CreateEventEx创建的事件初始状态是非激活的
参见主工程EventMain中界面上“Test CreateEventEx InitState”按钮上效果
- HANDLEtestHandle=::CreateEventEx(NULL,"TestCreateEventEx",CREATE_EVENT_MANUAL_RESET,EVENT_ALL_ACCESS);
- DWORDresult=::WaitForSingleObject(testHandle,1000);
(4) CreateEventEx比CreateEvent多了一个dwDesiredAccess,用来标识访问属性
或者是通过OpenEvent打开一个已经存在的事件
- HANDLE WINAPI OpenEvent(
- _In_ DWORD dwDesiredAccess,
- _In_ BOOL bInheritHandle,
- _In_ LPCTSTR lpName //CreateEvent中指定的名称
- );
2.成长
较为简单
(1)SetEvent激活事件状态
(2)ResetEvent重置事件状态
(3)PaulseEvent,我觉得我们可以忽略它
3.死亡
事件属于内核对象,Windows平台对于内核对象的管理采用“引用计数”这个方式
(1) 通过CreateEvent创建完成后该事件内核对象的引用计数为1
(2) 以后每OpenEvent一次,该事件对象的引用计数就加1
(3) 每CloseHandle一次,该事件对象的引用计数就减1
(4) 当内核对象的引用计数为0时,操作系统就回收该内核资源
为此我们准备了两个进程(详见工程下载),一个进程EventMain承担上述进程M的责任,另一个进程EventSubA承担进程A的责任,下面我们来做些实验验证以上说法,两个进程界面截图如下:
(1) M创建事件,M关闭事件,M再打开事件,失败很好理解
(2) M创建事件后,使M崩溃,A打开该事件失败.说明进程结束以后,系统会自动将该进程拥有的引用计数减去
(3) M创建事件后,A打开该事件,M崩溃,A再打开成功A打开事件后,该事件引用计数变为2,M崩溃后计数为1,故系统不回收
三.API汇总
第二部分其实已经包含了所有的API
ID | API | 功能 |
1 | CreateEvent | 创建事件对象 |
2 | CreateEventEx |
|
3 | OpenEvent | 打开事件对象 |
4 | SetEvent | 置为激活状态 |
5 | ResetEvent | 置为非激活状态 |
6 | PaulseEvent | 产生事件脉冲 |
7 | CloseHandle | 关闭windows内核对象都用这个函数 |
四.使用事件实现进程通信
1.设计
(1)两个子进程在后台等待事件发生
(2)主进程激活事件,相当于发出命令
(3)子进程收到命令,激活响应事件,相当于响应
(4)由于事件本身不具备传输数据的能力,所以这里只能传输命令
2.实现
(1)主进程创建八个事件
Work1和Work2用来区分不同的命令
前缀A和B后来区分发给哪个进程,以及响应是哪个进程发来的
从这里我们也可以发现,由于事件本身无法传输标志位,导致需要创建很多事件来区分不同的命令或响应
AWork1,主进程向子进程A发送AWork1命令的事件
AWork2,主进程向子进程A发送AWork2命令的事件
BWork1,主进程向子进程B发送BWork1命令的事件
BWork2,主进程向子进程B发送BWork2命令的事件
AResponse1,子进程A向主进程响应AResponse1的事件
AResponse2,子进程A向主进程响应AWork2命令的事件
BResponse1,子进程B向主进程响应Work1命令的事件
BResponse2,子进程B向主进程响应Work2命令的事件
之后Set相关命令事件
(2)然后A子进程wait到主进程发来的命令,并将Set响应事件
(3)主进程wait到响应事件
贴上主进程的关键代码
- voidCEventMainDlg::OnBnClickedButton9()
- {
- if(!isOpenSubProcess)
- {
- this->openSubProcess();
- Sleep(1000);
- }
- if(this->m_commandCTL.GetCurSel()==0)
- {
- if(this->m_processCTL.GetCurSel()==0)
- {
- ::SetEvent(commandHandleA[0]);
- if(WAIT_OBJECT_0==::WaitForSingleObject(responseHandleA[0],2000))
- {
- MessageBox("receive A response Work1 success","Info",MB_OK);
- }
- else
- {
- MessageBox("receive A response Work1 failed","Info",MB_OK);
- }
- }
- else
- {
- ::SetEvent(commandHandleB[0]);
- if(WAIT_OBJECT_0==::WaitForSingleObject(responseHandleB[0],2000))
- {
- MessageBox("receive B response Work1 success","Info",MB_OK);
- }
- else
- {
- MessageBox("receive B response Work1 failed","Info",MB_OK);
- }
- }
- }
- else
- {
- if(this->m_processCTL.GetCurSel()==0)
- {
- ::SetEvent(commandHandleA[1]);
- if(WAIT_OBJECT_0==::WaitForSingleObject(responseHandleA[1],2000))
- {
- MessageBox("receive A response Work2 success","Info",MB_OK);
- }
- else
- {
- MessageBox("receive A response Work2 failed","Info",MB_OK);
- }
- }
- else
- {
- ::SetEvent(commandHandleB[1]);
- if(WAIT_OBJECT_0==::WaitForSingleObject(responseHandleB[1],2000))
- {
- MessageBox("receive B response Work2 success","Info",MB_OK);
- }
- else
- {
- MessageBox("receive B response Work2 failed","Info",MB_OK);
- }
- }
- }
- ::ResetEvent(commandHandleA[0]);
- ::ResetEvent(commandHandleA[1]);
- ::ResetEvent(commandHandleB[0]);
- ::ResetEvent(commandHandleB[1]);
- }
贴上子进程A的关键代码
- voidthreadFunA(void* Param)
- {
- HANDLEcommandHandle[2]={NULL,NULL};
- commandHandle[0]=::OpenEvent(EVENT_ALL_ACCESS,TRUE,"AWork1");
- commandHandle[1]=::OpenEvent(EVENT_ALL_ACCESS,TRUE,"AWork2");
- HANDLEresponseHandle1=::OpenEvent(EVENT_ALL_ACCESS,TRUE,"AResponse1");
- HANDLEresponseHandle2=::OpenEvent(EVENT_ALL_ACCESS,TRUE,"AResponse2");
- while(true)
- {
- DWORDresult=::WaitForMultipleObjects(2,commandHandle,FALSE,INFINITE);
- if( WAIT_OBJECT_0 == result )
- {
- ::SetEvent(responseHandle1);
- ::ResetEvent(commandHandle[0]);
- }
- else
- {
- if(result==(WAIT_OBJECT_0 +1))
- {
- ::SetEvent(responseHandle2);
- ::ResetEvent(commandHandle[1]);
- }
- }
- }
- }
五.总结
1.事件本身和自定义消息在跨进程环境中都不太适合传输数据,只适合传输命令,
但事件是毫无传数据的能力,自定义消息至少还能传传简单数据。
2.和WM_COPYDATA以及剪贴板相比,事件可以实现同时向两个后台进程发送命令。
虽然这里我们区分了,但是实际上只要连个后台进程同时wait同一个事件,
当主进程Set该事件时,两个后台进程可同时接收到。
3.事件更适合用来互斥和同步,当要传输数据的时候,应该配合其他机制来实现