1. 程式人生 > >C++多執行緒同步之事件(Event)

C++多執行緒同步之事件(Event)

一、事件(Event)原理解析

1、執行緒同步Event,主要用於執行緒間的等待通知。
2、核心物件中,事件核心物件是個最基本的物件。
3、事件包含一個使用計數(與所有核心物件一樣),一個用於指明該事件是個自動重置的事件還是人工重置的事件的布林值,另一個用於指明該事件處於已通知狀態還是未通知狀態的布林值。
4、事件能夠通知一個操作已經完成。
5、有兩種不同型別的事件物件。一種是人工重置的事件,另一種是自動重置的事件。當人工重置的事件得到通知時,等待該事件的所有執行緒均變為可排程執行緒。當一個自動重置的事件得到通知時,等待該事件的執行緒中只有一個執行緒變為可排程執行緒。
6、當一個執行緒執行初始化操作,然後通知另一個執行緒執行剩餘的操作時,事件使用得最多。
7、事件初始化為未通知狀態,然後,當該執行緒完成它的初始化操作後,它就將事件設定為已通知狀態。這時,一直在等待該事件的另一個執行緒發現該事件已經得到通知,因此它就變成可排程執行緒。

二、Win32平臺原始碼

1、相關標頭檔案及API

函式名 函式說明
CreateEvent Creates or opens a named or unnamed event object.
CreateEventEx Creates or opens a named or unnamed event object and returns a handle to the object.
OpenEvent Opens an existing named event object.
PulseEvent Sets the specified event object to the signaled state and then resets it to the nonsignaled state after releasing the appropriate number of waiting threads.
ResetEvent Sets the specified event object to the nonsignaled state.
SetEvent Sets the specified event object to the signaled state.
/*標頭檔案*/
#include<windows.h>
HANDLE  CreateEvent(
LPSECURITY_ATTRIBUTESlpEventAttributes,     // 安全屬性
BOOLbManualReset,                           // 復位方式
BOOLbInitialState, // 初始狀態 LPCTSTRlpName // 物件名稱 );

引數:
lpEventAttributes[In]: 一個指向SECURITY_ATTRIBUTES結構的指標,確定返回的控制代碼是否可被子程序繼承。如果lpEventAttributes是NULL,此控制代碼不能被繼承。Windows NT/2000:lpEventAttributes的結構中的成員為新的事件指定了一個安全符。如果lpEventAttributes是NULL,事件將獲得一個預設的安全符。
bManualReset[In]: 指定將事件物件建立成手動復原還是自動復原。如果是TRUE,那麼必須用ResetEvent函式來手工將事件的狀態復原到無訊號狀態。如果設定為FALSE,當事件被一個等待執行緒釋放以後,系統將會自動將事件狀態復原為無訊號狀態。
bInitialState[In]: 指定事件物件的初始狀態。如果為TRUE,初始狀態為有訊號狀態;否則為無訊號狀態。
lpName[In]:指定事件的物件的名稱,是一個以0結束的字串指標。名稱的字元格式限定在MAX_PATH之內。名字是對大小寫敏感的。 如果lpName指定的名字,與一個存在的命名的事件物件的名稱相同,函式將請求EVENT_ALL_ACCESS來訪問存在的物件。這時候,由於bManualReset和bInitialState引數已經在建立事件的程序中設定,這兩個引數將被忽略。如果lpEventAttributes是引數不是NULL,它將確定此控制代碼是否可以被繼承,但是其安全描述符成員將被忽略。 如果lpName為NULL,將建立一個無名的事件物件。 如果lpName的和一個存在的訊號、互斥、等待計時器、作業或者是檔案對映物件名稱相同,函式將會失敗,在GetLastError函式中將返回ERROR_INVALID_HANDLE。造成這種現象的原因是這些物件共享同一個名稱空間。
返回值:如果函式呼叫成功,函式返回事件物件的控制代碼。如果對於命名的物件,在函式呼叫前已經被建立,函式將返回存在的事件物件的控制代碼,而且在GetLastError函式中返回ERROR_ALREADY_EXISTS。如果函式失敗,函式返回值為NULL,如果需要獲得詳細的錯誤資訊,需要呼叫GetLastError。

HANDLE WINAPI CreateEventEx(
__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
__in_opt LPCTSTR lpName,
__in DWORD dwFlags,
__in DWORD dwDesiredAccess
);

引數:
lpEventAttributes[in, optional] :一個指向SECURITY_ATTRIBUTES結構的指標,如果該引數設為NULL,那麼事件核心物件的控制代碼不能被子程序繼承.
lpName[in, optional] :指向事件核心物件的名稱字串的指標,如果該引數設為NULL,那麼這個物件被建立為一個匿名事件核心物件.
dwFlags[in] :這個引數可被設為以下一個或多個值:CREATE_ EVENT_ INITIAL_ SET 0x00000002 表示物件初始狀態為已觸發,否則為未觸發;CREATE_ EVENT_ MANUAL_RESET 0x00000001 表示這個事件物件必須用ResetEvents函式手動重置,如果不設定這個標誌,系統會在核心物件被釋放後自動重置.
dwDesiredAccess[in] :訪問許可權描述標記。
返回值:如果函式呼叫成功,返值是所建立或開啟的事件核心物件的控制代碼.如果呼叫失敗則返回NULL。

HANDLE OpenEvent(
                DWORD dwDesiredAccess,
                BOOL bInheritHandle,
                LPCTSTR lpName
                );

引數說明:
dwDesiredAccess [in]:指定對事件物件的請求訪問許可權,如果安全描述符指定的物件不允許要求通過對呼叫該函式的過程,函式將返回失敗。該引數必須設定為以下值:EVENT_ALL_ACCESS 指定事件物件所有可能的許可權。
bInheritHandle [in]:指定是否返回的控制代碼是否繼承 。該引數必須設定為false。
lpName[in]:指向一個以null結束的字串,即將要開啟的事件物件的名字。名稱是區分大小寫的。
返回值:函式執行成功則返回事件物件的控制代碼;失敗則返回NULL,獲取錯誤資訊可以使用GetLastError。

DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);

引數:
hHandle[in]:物件控制代碼。可以指定一系列的物件,如Event、Job、Memory resource notification、Mutex、Process、Semaphore、Thread、Waitable timer等。
dwMilliseconds[in]:定時時間間隔,單位為milliseconds(毫秒).如果指定一個非零值,函式處於等待狀態直到hHandle標記的物件被觸發,或者時間到了。如果dwMilliseconds為0,物件沒有被觸發訊號,函式不會進入一個等待狀態,它總是立即返回。如果dwMilliseconds為INFINITE,物件被觸發訊號後,函式才會返回。

DWORD WaitForMultipleObjects(  
  DWORD nCount,             // number of handles in the handle array  
  CONST HANDLE *lpHandles,  // pointer to the object-handle array  
  BOOL fWaitAll,            // wait flag  
  DWORD dwMilliseconds      // time-out interval in milliseconds  
); 

引數:
nCount: 控制代碼的數量 最大值為MAXIMUM_WAIT_OBJECTS(64)
HANDLE: 控制代碼陣列的指標。 HANDLE 型別可以為(Event,Mutex,Process,Thread,Semaphore )陣列 。
BOOL bWaitAll: 等待的型別,如果為TRUE 則等待所有訊號量有效在往下執行,FALSE 當有其中一個訊號量有效時就向下執行。
DWORD dwMilliseconds: 超時時間 超時後向執行。 如果為WSA_INFINITE 永不超時。如果沒有訊號量就會在這死等。

另外:
一個Event被建立以後,可以用OpenEvent()**API來獲得它的Handle,用**CloseHandle() 來關閉它,用SetEvent()PulseEvent()來設定它使其有訊號,用ResetEvent() 來使其無訊號,用WaitForSingleObject()WaitForMultipleObjects()來等待 其變為有訊號。
PulseEvent()是一個比較有意思的使用方法,正如這個API的名字,它使一個Event 物件的狀態發生一次脈衝變化,從無訊號變成有訊號再變成無訊號,而整個操作是原子的. 對自動復位的Event物件,它僅釋放第一個等到該事件的thread(如果有),而對於 人工復位的Event物件,它釋放所有等待的thread.

3、Win32原始碼

/************************MyEvent.h******************************/
#ifndef _MY_EVENT_H
#define _MY_EVENT_H

#include <windows.h>

class CMyEvent
{
public:
    CMyEvent()
    {
        m_hEvent = CreateEvent(NULL     /*安全屬性指標*/
        , false                         /*復位方式*/
        , true                          /*初始化狀態*/
        , NULL                          /*事件名稱*/
        );
        if(NULL == m_hEvent)
        {
            return;
        }
    }

    ~CMyEvent()
    {
        CloseHandle(m_hEvent);
    }

    void Lock()
    {
        WaitForSingleObject(m_hEvent,INFINITE);
    }

    void UnLock()
    {
        SetEvent(m_hEvent);
    }

private:
    HANDLE                  m_hEvent;
};

class CEventAutoLock
{
public:
    CEventAutoLock(CMyEvent* pMyEvent)
    : m_pMyEvent(pMyEvent)
    {
        if(NULL != m_pMyEvent)
        {
            m_pMyEvent->Lock();
        }
    }

    ~CEventAutoLock()
    {
        m_pMyEvent->UnLock();
    }

private:
    CMyEvent                *m_pMyEvent;
};

#endif
/****************************main.cpp****************************/
#include <iostream>
#include <windows.h>
#include "MySemaphore.h"
#include "MyMutex.h"
#include "MyCriticalSection.h"
#include "MyEvent.h"
using namespace std;

CMySemaphore                    g_MySemaphore;          //訊號量
CMyMutex                        g_MyMutex;              //互斥量
CMyCriticalSection              g_MyCriticalSection;    //臨界區
CMyEvent                        g_MyEvent;              //事件

DWORD WINAPI Fun(LPVOID lpParamter)
{
    string strPrint((const char*)lpParamter);
    int iRunTime = 0;
    //執行100次跳出
    while(++iRunTime<10)
    {
        {
            CEventAutoLock clock(&g_MyEvent);
            cout <<"["<< iRunTime <<"]:"<< strPrint.c_str()<<endl;
        }
    }
    return 0;
}

int main()
{
    //建立五個子執行緒
    string str1 = "A";
    string str2 = "B";
    string str3 = "C";
    string str4 = "D";
    string str5 = "E";

    HANDLE hThread1 = CreateThread(NULL, 0, Fun, (void*)str1.c_str(), 0, NULL);
    HANDLE hThread2 = CreateThread(NULL, 0, Fun, (void*)str2.c_str(), 0, NULL);
    HANDLE hThread3 = CreateThread(NULL, 0, Fun, (void*)str3.c_str(), 0, NULL);
    HANDLE hThread4 = CreateThread(NULL, 0, Fun, (void*)str4.c_str(), 0, NULL);
    HANDLE hThread5 = CreateThread(NULL, 0, Fun, (void*)str5.c_str(), 0, NULL);

    //關閉執行緒
    CloseHandle(hThread1);
    CloseHandle(hThread2);
    CloseHandle(hThread3);
    CloseHandle(hThread4);
    CloseHandle(hThread5);

    getchar();
//  system("pause");
    return 0;
}

執行結果:
這裡寫圖片描述

Linux平臺

在Linux平臺下沒有專門的Event(事件)物件,可以依靠Mutex實現和Event相同的功能。Mutex的使用可參見我前面的部落格。