Windows編程--線程和核心對象的同步-事件核心對象

來源:互聯網
上載者:User


在所有的核心對象中,事件核心對象是個最基本的對象。它們包含一個使用計數(與所有核心對象一樣),一個用於指明該事件是個自動重設的事件還是一個人工重設的事件的布爾值,另一個用於指明該事件處於已通知狀態還是未通知狀態布爾值

事件能夠通知一個操作已經完成。有兩種不同類型的事件對象。一種是人工重設的事件,另一種是自動重設的事件

當人工重設的事件得到通知時,等待該事件的所有線程均變為可調度線程。

當一個自動重設的事件得到通知時,等待該事件的線程中只有一個線程變為可調度線程。

當一個線程執行初始化操作,然後通知另一個線程執行剩餘的操作時,事件使用得最多。事件初始化為未通知狀態,然後,當該線程完成它的初始化操作後,它就將事件設定為已通知狀態。這時,一直在等待該事件的另一個線程發現該事件已經得到通知,因此它就變成可調度線程。這第二個線程知道第一個線程已經完成了它的操作。

下面是CreateEvent函數,用於建立事件核心對象:

 

HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOLfManualReset,
BOOLfInitialState,
PCTSTRpszName);

 

 

fMannualReset參數是個布爾值,它能夠告訴系統是建立一個人工重設的事件(TRUE)還是建立一個自動重設的事件(FALSE)。

fInitialState參數用於指明該事件是要初始化為已通知狀態(TRUE)還是未通知狀態(FALSE)。當系統建立事件對象後, createEvent就將與進程相關的控制代碼返回給事件對象。

createEvent就將與進程相關的控制代碼返回給事件對象。

其他進程中的線程可以獲得對該對象的訪問權,方法是使用在pszName參數中傳遞的相同值(命名物件),使用繼承性,使用DuplicateHandle函數等來調用CreateEvent,或者調用OpenEvent ,在pszName參數中設定一個與調用CreateEvent時設定的名字相匹配的名字:

 

 

HANDLEOpenEvent(
DWORD fdwAccess,
BOOL fInherit,
PCTSTR pszName
);

 

 

與所有情況中一樣,當不再需要事件核心對象時,應該調用CloseHandle函數。

如果這裡使用自動重設的事件而不是人工重設的事件,那麼應用程式的行為特性就有很大的差別。當主線程調用SetEvent之後,系統只允許一個輔助線程變成可調度狀態。同樣,也無法保證系統將使哪個線程變為可調度狀態。其餘兩個輔助線程將繼續等待。

已經變為可調度狀態的線程擁有對記憶體塊的獨佔訪問權。讓我們重新編寫線程的函數,使得每個函數在返回前調用SetEvent函數(就像WinMain函數所做的那樣)。這些線程函數現在變成下面的形式:

 

DWORD WINAPI WordCount(PVOID pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

//Access the memory block.
...
SetEvent(g_hEvent);
return(0);
}

DWORD WINAPI SpellCheck(PVOID pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

//Access the memory block.
...
SetEvent(g_hEvent);
return(0);
}

DWORD WINAPI GrammarCheck(PVOID pvParam)
{
//Wait until the file's data is in memory.
WaitForSingleObject(g_hEvent, INFINITE);

//Access the memory block.
...
SetEvent(g_hEvent);
return(0);
}

 

 

當線程完成它對資料的專門傳遞時,它就調用SetEvent函數,該函數允許系統使得兩個正在等待的線程中的一個成為可調度線程。同樣,我們不知道系統將選擇哪個線程作為可調度線程,但是該線程將進行它自己的對記憶體塊的專門傳遞。當該線程完成操作時,它也將調用SetEvent函數,使第三個即最後一個線程進行它自己的對記憶體塊的傳遞。注意,當使用自動重設事件時,如果每個輔助線程均以讀/寫方式訪問記憶體塊,那麼就不會產生任何問題,這些線程將不再被要求將資料視為唯讀資料。

 

BOOLPulseEvent(HANDLE hEvent);

PulseEvent函數使得事件變為已通知狀態,然後立即又變為未通知狀態,這就像在調用SetEvent後又立即調用ResetEvent函數一樣。如果在人工重設的事件上調用PulseEvent函數,那麼在發出該事件時,等待該事件的任何一個線程或所有線程將變為可調度線程。如果在自動重設事件上調用PulseEvent函數,那麼只有一個等待該事件的線程變為可調度線程。如果在發出事件時沒有任何線程在等待該事件,那麼將不起任何作用。

PulseEvent函數並不非常有用(可以先不管它)。

 

孫鑫筆記的一個例子

 

#include <windows.h>                                       
#include <iostream.h>
//首先做兩個線程,實現兩個線程間的同步上次是利用互斥對象實現線程間的同步CreateMutex函數,這次利用事件對象實現線程間的同步CreateEvent
DWORD WINAPI Fun1Proc(LPVOID lpParameter);
DWORD WINAPI Fun2Proc(LPVOID lpParameter);

int tickets = 100;
HANDLE g_hEvent; //1.定義一個事件控制代碼

int main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1=CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
hThread2=CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
CloseHandle(hThread1);
CloseHandle(hThread2);
//2.建立人工重製的匿名的事件對象,第一個是設定安全性,這裡不管,
// 第二個是BOOL類的設定事件的類型(人工重製TRUE,自動重設FALSE),
// 第三是初始化(TRUE(已通知狀態) FALSE(未通知狀態)。第四個是給事件
// 取名,
// 類似如互斥對象的,取名之後就只能開取一個執行個體了

// g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL); // 3.設定為自動重設
g_hEvent=CreateEvent(NULL, FALSE, FALSE, "tickets"); // 給這個對象取名叫tickets
if (g_hEvent) // 判斷對象是否存在
{
if (ERROR_ALREADY_EXISTS == GetLastError()) // 當程式檢測到當前的對象與你開啟的執行個體對象相同的時候
// 就會返回ERROR_ALREADY_EXISTS,如果接收到這個資訊就說明你將
// 開啟一個已經開啟的執行個體
{
cout << "不能開啟一個執行個體!" << endl;
return 0; // 直接返回,程式不往下執行
}

}
SetEvent(g_hEvent); // 4.設定為已通知狀態有一個線程可以調用
Sleep(10000);

CloseHandle(g_hEvent);
return 0;
}


DWORD WINAPI Fun1Proc(LPVOID lpParameter)  //如果選擇人工重製的事件對象,
//當這個事件處於有訊號狀態的時候所有等待該訊號的線程都可以調用,
//所以這樣是不行的
 // 事件對象和互斥對象差不多,都是誰擁有誰釋放,
// 但是由於它自身的特性,只要對象處於有訊號狀態
// 所有的線程都可以訪問,所以才不能用人工重製
 {
while (TRUE)
{
//5.線程等待事件訊號,線程執行
WaitForSingleObject(g_hEvent,INFINITE);//等待事件對象   //設定成自動重製之後發現只有一個線程運行
    // 且只運行一次就中止線程,這是因為當自動重製的事件對象被通知時,
// 只有一個等待該事件的線程變為可調度線程,
// 這裡就是線程變為可調度的,但是線程的WaitForSingleObject
// 得到訊號候往下執行,系統會將該訊號又設定為,這是系統維護的
  //線程睡眠到線程的時候得不到訊號就等待,再到線程執行玩,
// 之後就沒有訊號了,線程.2都等待一直到主線程睡眠事件到了,
// 然後結束進程,我們可以線上程執行完後將事件對象設定為有訊號
if (tickets > 0)
{
Sleep( 1 );
cout << "Thread1 sell tickets:" << tickets-- << endl;
}
else
{
break;
}
SetEvent(g_hEvent); //6.線程運行完之後發生等待成功副作用將事件對象設定為未通知訊號
}

return 0;
}


DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{

while(TRUE)
{
WaitForSingleObject(g_hEvent, INFINITE); //7.線程等待事件訊號,等待到線程可調度執行
if(tickets > 0)
{
Sleep( 1 );
cout << "Thread2 sell tickets:" << tickets-- << endl;
}
else
{
break;
}
SetEvent(g_hEvent);   // 8.線程運行完之後發生等待成功副作用將事件對象
// 設定為未通知訊號。這樣就解決了問題,
// 一定要注意區分是人工重製的還是自動重製的
  // 自動重製的事件對象能構實現只允許一個線程訪問共有資源,
// 就是它在把訊號交給一個線程之後就將訊號設定為空白,
// 其他的線程得不到訊號就無法在該線程執行的時候訪問資源了,
// 但是在該線程執行完保護的代碼之後,我們一定要
// 將訊號重新設定為有訊號狀態。
}
return 0; // 下面來看看命名的事件對象只能建立一個執行個體

}

 

 

 

 

FangSH 2011-01-01

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.