windows多執行緒同步--事件

tenos發表於2014-03-14

推薦參考部落格:秒殺多執行緒第六篇 經典執行緒同步 事件Event

 

事件是核心物件,多用於執行緒間通訊,可以跨程式同步

事件主要用到三個函式:CreateEvent,OpenEvent,SetEvent,ResetEvent                                                        本文地址

 

CreateEvent

函式功能:建立事件

函式原型:

HANDLECreateEvent(

LPSECURITY_ATTRIBUTESlpEventAttributes,

BOOLbManualReset,

BOOLbInitialState,

LPCTSTRlpName

);

第一個引數:表示安全控制,一般直接傳入NULL。

第二個引數:確定事件是手動置位還是自動置位,傳入TRUE表示手動置位,傳入FALSE表示自動置位。如果為自動置位,則對該事件呼叫WaitForSingleObject()後會自動呼叫ResetEvent()使事件變成未觸發狀態。打個小小比方,手動置位事件相當於教室門,教室門一旦開啟(被觸發),所以有人都可以進入直到老師去關上教室門(事件變成未觸發)。自動置位事件就相當於醫院裡拍X光的房間門,門開啟後只能進入一個人,這個人進去後會將門關上,其它人不能進入除非門重新被開啟(事件重新被觸發)。

第三個引數:表示事件的初始狀態,傳入TRUR表示已觸發。

第四個引數:表示事件的名稱,傳入NULL表示匿名事件。

返回值:事件的控制程式碼

 

OpenEvent

函式功能:根據名稱獲得一個事件控制程式碼。

函式原型:

HANDLEOpenEvent(

DWORDdwDesiredAccess,

BOOLbInheritHandle,

LPCTSTRlpName     //名稱

);

函式說明:

第一個引數:表示訪問許可權,對事件一般傳入EVENT_ALL_ACCESS。詳細解釋可以檢視MSDN文件。

第二個引數:表示事件控制程式碼繼承性,一般傳入TRUE即可。

第三個引數:表示名稱,不同程式中的各執行緒可以通過名稱來確保它們訪問同一個事件。

返回值:返回事件的控制程式碼

 

SetEvent

函式功能:觸發事件

函式原型:BOOLSetEvent(HANDLEhEvent);

函式說明:每次觸發後,必有一個或多個處於等待狀態下的執行緒變成可排程狀態。

 

ResetEvent

函式功能:將事件設為末觸發

函式原型:BOOLResetEvent(HANDLEhEvent);

 

下面從一個例子說明:假設有三個執行緒都需要使用印表機,我們可以使用互斥量來控制,這樣就可以保證每次只有一個執行緒在使用印表機

使用自動置位,那麼在呼叫WaitForSingleObject()後會自動呼叫ResetEvent()使事件變為未觸發狀態,為了使後面的執行緒能夠繼續列印,需要線上程函式的結尾呼叫SetEvent來觸發事件

 #include<string>
 #include<iostream>
 #include<process.h>
 #include<windows.h>
 using namespace std;

 //宣告事件控制程式碼
 HANDLE hev;

//執行緒繫結的函式返回值和引數是確定的,而且一定要__stdcall
unsigned __stdcall threadFun(void *param)
{
    WaitForSingleObject(hev, INFINITE);//等待事件被觸發
    for(int i = 0; i < 10; i++)
        cout<<*(string *)(param)<<" ";
    cout<<endl;
    SetEvent(hev);//設定事件為觸發狀態,使後面的執行緒可以列印
    return 1;
}


int main()
{
    //建立一個未被觸發的事件,事件是自動置位的
    hev = CreateEvent(NULL, FALSE, FALSE, NULL);

    SetEvent(hev);// 觸發事件,使執行緒可以列印

    HANDLE hth1, hth2, hth3;
    string s1 = "first", s2 = "second", s3 = "third";

    //建立執行緒
    hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, &s1, 0, NULL);
    hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, &s2, 0, NULL);
    hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFun, &s3, 0, NULL);

    //等待子執行緒結束
    WaitForSingleObject(hth1, INFINITE);
    WaitForSingleObject(hth2, INFINITE);
    WaitForSingleObject(hth3, INFINITE);

    //一定要記得關閉執行緒控制程式碼
    CloseHandle(hth1);
    CloseHandle(hth2);
    CloseHandle(hth3);

    //千萬別忘了刪除事件
    CloseHandle(hev);
}

 

image

 

使用手動置位,呼叫WaitForSingleObject()後事件就一直是觸發狀態,執行緒可以任意的列印

 #include<string>
 #include<iostream>
 #include<process.h>
 #include<windows.h>
 using namespace std;

 //宣告事件控制程式碼
 HANDLE hev;

//執行緒繫結的函式返回值和引數是確定的,而且一定要__stdcall
unsigned __stdcall threadFun(void *param)
{
    WaitForSingleObject(hev, INFINITE);//等待事件被觸發
    for(int i = 0; i < 10; i++)
        cout<<*(string *)(param)<<" ";
    cout<<endl;
    //SetEvent(hev);//設定事件為觸發狀態,使後面的執行緒可以列印
    return 1;
}


int main()
{
    //建立一個未被觸發的事件,事件是手動置位的
    hev = CreateEvent(NULL, TRUE, FALSE, NULL);

    SetEvent(hev);// 觸發事件,使執行緒可以列印

    HANDLE hth1, hth2, hth3;
    string s1 = "first", s2 = "second", s3 = "third";

    //建立執行緒
    hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFun, &s1, 0, NULL);
    hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFun, &s2, 0, NULL);
    hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFun, &s3, 0, NULL);

    //等待子執行緒結束
    WaitForSingleObject(hth1, INFINITE);
    WaitForSingleObject(hth2, INFINITE);
    WaitForSingleObject(hth3, INFINITE);

    //一定要記得關閉執行緒控制程式碼
    CloseHandle(hth1);
    CloseHandle(hth2);
    CloseHandle(hth3);

    //千萬別忘了刪除事件
    CloseHandle(hev);
}

 

image

 

再通過下面的例子來看看時間有沒有所有權屬性:編寫一個程式,開啟3個執行緒,這3個執行緒的ID分別為A、B、C,每個執行緒將自己的ID在螢幕上列印10遍,要求輸出結果必須按ABC的順序顯示;如:ABCABC….依次遞推

 #include<string>
 #include<iostream>
 #include<process.h>
 #include<windows.h>
 using namespace std;
 //宣告3個事件控制程式碼
HANDLE  hev1, hev2, hev3;

//執行緒繫結的函式返回值和引數是確定的,而且一定要__stdcall
unsigned __stdcall threadFunA(void *)
{
    for(int i = 0; i < 10; i++){
        WaitForSingleObject(hev1, INFINITE);//等待事件1
        cout<<"A";
        SetEvent(hev2);//觸發事件2
    }
    return 1;
}
unsigned __stdcall threadFunB(void *)
{
    for(int i = 0; i < 10; i++){
        WaitForSingleObject(hev2, INFINITE);//等待事件2
        cout<<"B";
        SetEvent(hev3);//觸發事件3
    }
    return 2;
}
unsigned __stdcall threadFunC(void *)
{
    for(int i = 0; i < 10; i++){
        WaitForSingleObject(hev3, INFINITE);//等待事件3
        cout<<"C";
        SetEvent(hev1);//觸發事件1
    }
    return 3;
}


int main()
{
    hev1 = CreateEvent(NULL, FALSE, FALSE, NULL);
    hev2 = CreateEvent(NULL, FALSE, FALSE, NULL);
    hev3 = CreateEvent(NULL, FALSE, FALSE, NULL);
    SetEvent(hev1);//觸發事件1,從A開始列印

    HANDLE hth1, hth2, hth3;
    //建立執行緒
    hth1 = (HANDLE)_beginthreadex(NULL, 0, threadFunA, NULL, 0, NULL);
    hth2 = (HANDLE)_beginthreadex(NULL, 0, threadFunB, NULL, 0, NULL);
    hth3 = (HANDLE)_beginthreadex(NULL, 0, threadFunC, NULL, 0, NULL);

    //等待子執行緒結束
    WaitForSingleObject(hth1, INFINITE);
    WaitForSingleObject(hth2, INFINITE);
    WaitForSingleObject(hth3, INFINITE);

    //一定要記得關閉執行緒控制程式碼
    CloseHandle(hth1);
    CloseHandle(hth2);
    CloseHandle(hth3);

    //刪除事件
    CloseHandle(hev1);
    CloseHandle(hev2);
    CloseHandle(hev3);
}

 

image

 

由結果可知事件不具有所有權屬性,即某個執行緒獲取事件後,一定要等待事件再次被觸發。可參考本部落格其他文章中臨界區、互斥量、訊號量的所有權屬性來理解。

 

【版權宣告】轉載請註明出處http://www.cnblogs.com/TenosDoIt/p/3601458.html

相關文章