Windows进程间同步方法使用Event

本文详细介绍了Windows操作系统中Event内核对象的使用,包括如何创建、打开、等待、设置和重置事件,以及其在进程间同步中的作用。通过示例代码展示了如何在不同进程中使用Event进行通信,解释了自动重置和手动重置事件的区别,并提到了不同权限用户进程间使用Event的注意事项。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值