背景
當創建一個線程時,其實那個線程是一個循環。這樣就帶來了一個問題,在那個死循環裏要找到合適的條件退出那個死循環,那麼是怎麼樣實現它的呢?在Windows裏往往是採用事件的方式,當然還可以採用其它的方式。在這裏先介紹採用事件的方式來通知從線程運行函數退出來,它的實現原理是:在那個死循環裏不斷地使用Wait(poco庫裏的wait函數)函數來檢查事件是否滿足,如果滿足就退出線程,不滿足就繼續運行。
通過函數SetEvent/ResetEvent分別將EVENT設置爲發信號與不發信號。
wait函數在事件所指定的對象(該對象可以是類,也可以是事件本身)爲有信號狀態時才繼續運行。
CreateEvent
創建事件函數CreateEvent,原型如下:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全屬性
BOOL bManualReset, // 復位方式,1爲手動復位,0爲自動復位
BOOL bInitialState, // 初始狀態,0爲不可用狀態即無信號,1爲可用狀態即有信號
LPCTSTR lpName // 對象名稱
);
【參數】
- lpEventAttributes
一個指向SECURITY_ATTRIBUTES結構的指針,確定返回的句柄是否可被子進程繼承。如果lpEventAttributes是NULL,此句柄不能被繼承; - bManualReset
指定將事件對象創建成手動復原還是自動復原。如果是TRUE,那麼必須ResetEvent函數來手工將事件的狀態復原到無信號狀態。如果設置爲FALSE,當事件被一個等待線程釋放以後,系統將會自動將事件狀態復原爲無信號狀態; - bInitialState
指定事件對象的初始狀態。如果爲TRUE,初始狀態爲有信號狀態;否則爲無信號狀態; - lpName
指定事件的對象的名稱,是一個以0結束的字符串指針。名稱的字符格式限定在MAX_PATH之內。名字是對大小寫敏感的。
如果lpName指定的名字,與一個存在的命名的事件對象的名稱相同,函數將請求EVENT_ALL_ACCESS來訪問存在的對象。這時候,由於bManualReset和bInitialState參數已經在創建事件的進程中設置,這兩個參數將被忽略。如果lpEventAttributes是參數不是NULL,它將確定此句柄是否可以被繼承,但是其安全描述符成員將被忽略。
如果lpName爲NULL,將創建一個無名的事件對象。
如果lpName的和一個存在的信號、互斥、等待計時器、作業或者是文件映射對象名稱相同,函數將會失敗,在GetLastError函數中將返ERROR_INVALID_HANDLE。造成這種現象的原因是這些對象共享同一個命名空間;
【返回值】 - WAIT_OBJECT_0 :表示等待對象已經變爲有信號狀態,如果設置爲自動復位,還會把此信號再次變爲無信號狀態;
- WAIT_TIMEOUT :表示等待超時;
- WAIT_FAILED :表示等待對象句柄是一個無效句柄;
CreateEvent實現多線程
主要測試CreateEvent中bManualReset:和bInitialState參數的取值在線程調用中信號狀態的情況。
Test_one:
bManualReset:TRUE
bInitialState:TRUE
CreateEvent(NULL, TRUE, TRUE, NULL); //使用手動重置爲無信號狀態,初始化時有信號狀態
#include "iostream"
#include "windows.h"
using namespace std;
DWORD WINAPI ThreadProc1(LPVOID lpParam);
DWORD WINAPI ThreadProc2(LPVOID lpParam);
HANDLE hEvent = NULL;
HANDLE hThread1 = NULL;
HANDLE hThread2 = NULL;
int main(int argc, char *args[])
{
hEvent = CreateEvent(NULL, TRUE, TRUE, NULL)</span>; //使用手動重置爲無信號狀態,初始化時有信號狀態
//hEvent = CreateEvent(NULL, FALSE, TRUE, NULL); //當一個等待線程被釋放時,自動重置爲無信號狀態,初始是有信號狀態
//if (SetEvent(hEvent))
//{
// cout << "setEvent 成功" <<endl;
//}
hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc1, NULL, 0,NULL);
Sleep(200);
hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc2, NULL, 0,NULL);
Sleep(200);
if ( NULL == hThread1)
{
cout <<"create thread fail!";
}
//DWORD dCount = ResumeThread(hThread);
//cout << LOWORD(dCount) << endl;
return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
cout <<"in thread1@!"<<endl;
DWORD dReturn = WaitForSingleObject(hEvent,INFINITE);
if ( WAIT_OBJECT_0 == dReturn)
{
cout <<" thread1 signaled ! "<<endl;
}
cout <<"in thread1 --signal"<<endl;
//SetEvent(hEvent);
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
cout <<"in thread2@!"<<endl;
DWORD dReturn = WaitForSingleObject(hEvent,INFINITE);
if ( WAIT_OBJECT_0 == dReturn)
{
cout <<"thread2 signaled ! "<<endl;
}
cout <<"in thread2--signal"<<endl;
return 0;
}
運行結果:
in thread1@!
thread1 signaled !
in thread1 --signal
in thread2@!
thread2 signaled !
in thread2 --signal
從結果中看,執行完線程1又執行了線程2.
由於hEvent = CreateEvent(NULL, TRUE, TRUE, NULL),使用手動重置爲無信號狀態,初始化時有信號狀態
所以hEvent一直處於有信號狀態,在線程1釋放後,hEvent仍處於有信號狀態,所以線程2正常執行。
Test_two:
bManualReset:FALSE
bInitialState:TRUE
hEvent = CreateEvent(NULL, FALSE, TRUE, NULL); //當一個等待線程被釋放時,自動重置爲無信號狀態,初始是有信號狀態
運行結果:
in thread1@!
thread1 signaled !
in thread1 --signal
in thread2@!
從執行結果中分析,執行了線程1,線程2一直在等待,直到主線程結束。
由於hEvent = CreateEvent(NULL, FALSE, TRUE, NULL),當一個等待線程被釋放時,自動重置爲無信號狀態,初始是有信號狀態。
初始執行線程1的時候,hEvent是有信號的,所以線程1正常執行;又由於bManualReset=FALSE,所以執行完線程1後,事件自動重置爲無信號狀態,所以線程2一直在等待
WaitForSingleObject(hEvent,INFINITE); 函數一直在等待hEvent變爲有信號狀態,但是當主線程執行完,還沒等待到,線程2程序一直沒有走下去。
Test_three:
bManualReset:TRUE
bInitialState:FALSE
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//使用手動重置爲無信號狀態,初始化時爲無信號狀態
執行結果是:
in thread1@!
in thread2@!
此時,如果放開例子中的註釋部分:
if (SetEvent(hEvent)) //設置信號爲有信號狀態
{
cout << "setEvent 成功" <<endl;
}
可見,線程1和線程2都執行了。
因爲調用SetEvent,事件變爲有信號狀態,線程1執行;又由於線程1釋放後,hEvent仍舊處於有信號狀態,所以線程2也執行了。
再修改:在線程1中,添加ResetEvent(hEvent)(手動設置事件爲無信號狀態),則線程2不會執行。
Test_four:
bManualReset:FALSE
bInitialState:FALSE
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//線程釋放後自動重置爲無信號狀態,初始化時爲無信號狀態
#include "iostream"
#include "windows.h"
using namespace std;
DWORD WINAPI ThreadProc1(LPVOID lpParam);
DWORD WINAPI ThreadProc2(LPVOID lpParam);
HANDLE hEvent = NULL;
HANDLE hThread1 = NULL;
HANDLE hThread2 = NULL;
int main(int argc, char *args[])
{
//hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); //使用手動重置爲無信號狀態,初始化時有信號狀態
//hEvent = CreateEvent(NULL, FALSE, TRUE, NULL); //當一個等待線程被釋放時,自動重置爲無信號狀態,初始是有信號狀態
//hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//使用手動重置爲無信號狀態,初始化時爲無信號狀態
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);//使用手動重置爲無信號狀態,初始化時爲無信號狀態
if (SetEvent(hEvent))
{
cout << "setEvent 成功" <<endl;
}
hThread1 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc1, NULL, 0,NULL);
Sleep(200);
hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc2, NULL, 0,NULL);
Sleep(200);
if ( NULL == hThread1)
{
cout <<"create thread fail!";
}
//DWORD dCount = ResumeThread(hThread);
//cout << LOWORD(dCount) << endl;
return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lpParam)
{
cout <<"in thread1@!"<<endl;
DWORD dReturn = WaitForSingleObject(hEvent,INFINITE);
if ( WAIT_OBJECT_0 == dReturn)
{
cout <<" thread1 signaled ! "<<endl;
}
cout <<"in thread1 --signal"<<endl;
//SetEvent(hEvent);
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParam)
{
cout <<"in thread2@!"<<endl;
DWORD dReturn = WaitForSingleObject(hEvent,INFINITE);
if ( WAIT_OBJECT_0 == dReturn)
{
cout <<"thread2 signaled ! "<<endl;
}
cout <<"in thread2--signal"<<endl;
return 0;
}
執行結果:
setEvent成功
in thread1@!
thread1 signaled !
in thread1 --signal
in thread2@!
由於調用SetEvent,hEvent爲有信號狀態,線程1正常執行,又由於調用完線程1後,hEvent自動重置爲無信號狀態,所以線程2只能在等待,直到主線程退出。
修改:線程1中的SetEvent(hEvent);的註釋去掉,再運行,則線程1和線程2 都會執行