線程同步之事件


事件:事件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;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM