线程同步之事件

事件:事件Event实际上是个内核对。事件类似于前面的信号量,一个事件有两种状态:激发状态和未激发状态。也称有信号状态和无信号状态。
事件又分两种类型:手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待中的线程,然后自动恢复为未激发状态。

自动Event可以被抽象为四个操作:
- 创建 CreateEvent(NULL,false,true,NULL);
- 带触发 WaitForSingleObject(g_hEvent, INFINITE);
- 重置激活 SetEvent(g_hEvent);
- 销毁 CloseHandle(g_hEvent);

 

手动Event可以被抽象为五个操作:
- 创建 CreateEvent(NULL,true,true,NULL);
- 带触发 WaitForSingleObject(g_hEvent, INFINITE);
- 重置未激活 ResetEvent(g_hEvent);
- 重置激活 SetEvent(g_hEvent);
- 销毁 CloseHandle(g_hEvent);

 

函数解析:

1.
函数功能描述:创建或打开一个命名的或无名的事件对象
函数原型:
HANDLE CreateEvent
(
  LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性
  BOOL bManualReset, // 复位方式
  BOOL bInitialState, // 初始状态
  LPCTSTR lpName // 对象名称
);
参数:
lpEventAttributes:
  一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承。如果
lpEventAttributes是NULL,事件将获得一个默认的安全符。

bManualReset:
  指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当事件被一个等待线程释放以后,系统将会自动将事件状态复原为无信号状态。

bInitialState:
  指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
lpName:
  指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。

注意:
1.如果lpName指定的名字,与一个存在的命名的事件对象的名称相同,函数将请求EVENT_ALL_ACCESS来访问存在的对象。这时候,由于bManualReset和bInitialState参数已经在创建事件的进程中设置,这两个参数将被忽略。如果lpEventAttributes是参数不是NULL,它将确定此句柄是否可以被继承,但是其安全描述符成员将被忽略。
2.如果lpName为NULL,将创建一个无名的事件对象。
3.如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。

注意点:

手动与自动的区别:
自动重置: SetEvent之后, 事件自动重置为未触发状态。
手动重置: SetEvent之后, 需要调用ResetEvent事件才置为未触发状态。

区别: 当一个手动重置事件被触发的时候, 正在等待该事件的所有线程都变为可调度状态; 当一个自动重置事件被触发的时候,
只有一个正在等待该事件的线程会变为可调度状态. 系统并不会保证会调度其中的哪个线程, 剩下的线程将继续等待. 这样, 可以在在每个线程函数返回之前调用SetEvent

换句话说:
(1)对于手动置位事件,所有正处于等待状态下线程都变成可调度状态。
(2)对于自动置位事件,所有正处于等待状态下线程只有一个变成可调度状态。

2.
函数原型:
DWORD WaitForSingleObject
(
  HANDLE hHandle,
  DWORD dwMilliseconds
);
参数解析:
参数hHandle是一个事件的句柄,第二个参数dwMilliseconds是时间间隔(INFINITE 永久)。如果时间是有信号状态返回。

WaitForSingleObject函数用来检测hHandle事件的信号状态。如果dwMilliseconds为有限事件,则当函数的执行时间超过dwMilliseconds就返回,
但如果参数dwMilliseconds为INFINITE时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到WaitForSingleObject有返回直才执行后面的代码。

注意:
(1)如果是自动置位事件,那么每一次WaitForSingleObject后,此时状态就会变成未激发状态,就要用SetEvent()进行激活。
(2)如果是手动置位事件,每一次WaitForSingleObject后,不会改变原有的状态。要利用ResetEvent()设置为未激活状态,再利用SetEvent()进行激活。

3.
SetEvent
函数功能:触发事件
函数原型:BOOL SetEvent(HANDLEhEvent);
函数说明:每次触发后,必有一个或多个处于等待状态下的线程变成可调度状态。

4.
ResetEvent
函数功能:将事件设为末触发
函数原型:BOOL ResetEvent(HANDLEhEvent);

 

源代码:
// Semaphore.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <Windows.h>
#include <process.h>

//线程数
#define  g_nThreadNum 5

//信号量
HANDLE g_hEvent;

//累加数
int g_nCount = 0;

unsigned _stdcall ThreadFunc(void * lParam)
{
    
    for (int i = 0; i < 100000; ++i)
    {
        
        //等待事件被触发  ,触发后变成无信号
        WaitForSingleObject(g_hEvent, INFINITE);
    
        g_nCount++;

        //重置事件为激活
        SetEvent(g_hEvent);
    }
    return 0;
}


int _tmain(int argc, _TCHAR* argv[])
{
    //创建事件
    g_hEvent=(HANDLE)CreateEvent(NULL,false,true,NULL);
    
    //启动线程
    HANDLE pThread[g_nThreadNum];
    for (int i = 0; i < g_nThreadNum; ++i)
    {
        
        pThread[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, 0);
        if (pThread[i] == 0)
        {
            continue;
            i--;
        }
    }

    //等待线程结束
    WaitForMultipleObjects(g_nThreadNum, pThread, TRUE, INFINITE);

    printf("g_nCount:%d\n", g_nCount);

    //释放资源
    for (int i = 0; i < g_nThreadNum; ++i)
    {
        CloseHandle(pThread[i]);
    }
    
    CloseHandle(g_hEvent);


    getchar();
    return 0;
}

 

posted @ 2017-10-19 18:37  gd_沐辰  阅读(1824)  评论(0编辑  收藏  举报