1.概述
事件對象就像一個開關:它只有兩種狀態---開和關。當一個事件處於”開”狀態,我們稱其爲”有信號”否則稱爲”無信號”。可以在一個線程的執行函數中創建一個事件對象,然後觀察它的狀態,如果是”無信號”就讓該線程睡眠,這樣該線程佔用的CPU時間就比較少。
2.CreateEvent函數
LPSECURITY_ATTRIBUTES lpEventAttributes, //一般爲NULL,屬性
BOOL bManualReset, // 重置類型,手動重置還是 自動重置
BOOL bInitialState, // 初始狀態,TRUE爲有信號,FALSE爲無信號
LPCTSTR lpName // 事件對象名
);
該函數創建一個Event同步對象,如果CreateEvent調用成功的話,會返回新生成的對象的句柄,否則返回NULL。
參數說明:
lpEventAttributes 一般爲NULL
bManualReset 創建的Event是自動復位還是人工復位.如果true,人工復位, 一旦該Event被設置爲有信號,則它一直會等到ResetEvent()被調用時纔會恢復爲無信號; 如果爲false,Event被設置爲有信號,則當有一個wait到它的Thread時, 該Event就會自動復位,變成無信號. 如果想在每次調用WaitForSingleObject 後讓WINDOWS爲您自動地把事件地狀態恢復爲”無信號”狀態,必須把該參數設爲FALSE,否則,您必須每次調用ResetEvent函數來清除事件的信號。
bInitialState 初始狀態,true,有信號,false無信號
lpName 事件對象的名稱。您在OpenEvent函數中可能使用。
3.Event相關
一個Event被創建以後,可以用OpenEvent()來獲得它的Handle,用CloseHandle()來關閉它(在主線程中使用CloseHandle()函數,很有可能導致子線程的Event對象信號控制機制失效,但需要將Event關閉,否則容易造成句柄泄漏問題,故合理使用關閉句柄函數);用SetEvent()或PulseEvent()來設置它使其有信號,用ResetEvent() 來使其無信號,用WaitForSingleObject()或WaitForMultipleObjects()來等待其變爲有信號.SetEvent(HANDLE hEvent ); PulseEvent(HANDLE hEvent ); ResetEvent(HANDLE hEvent );
PulseEvent()是一個比較有意思的使用方法,正如這個API的名字,它使一個Event 對象的狀態發生一次脈衝變化,從無信號變成有信號再變成無信號,而整個操作是原子的.對自動復位的Event對象,它僅釋放第一個等到該事件的thread(如果有),而對於人工復位的Event對象,它釋放所有等待的thread.
在事件對象生成後,必須調用WaitForSingleObject來讓線程進入等待狀態,該函數的語法如下:
WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
hHandle-->指向同步對象的指針。事件對象其實是同步對象的一種。
dwMilliseconds--> 等待同步對象變成”有信號”前等待的時間,以毫秒計。當等待的時間超過該值後無信號同步對象仍處於”無信號”狀態,線程不再等待, WaitForSingleObject函數會返回WAIT_TIMEOUT。如果想要線程一直等待,請把該參數設爲INFINITE(該值等於0xffffffff)爲一直等待。
4.示例
#include "stdafx.h"
#include <windows.h>
#include <iostream>
#include <string.h>
#include <sstream>
using namespace std;
#define NAME_LINE 40
HANDLE g_hMutex;
HANDLE g_hEvent;
//定義線程函數傳入參數的結構體
typedef struct __TICKET
{
int nCount;
char strTicketName[NAME_LINE];
__TICKET() : nCount(0)
{
memset(strTicketName, 0, NAME_LINE * sizeof(char));
}
}TICKET;
typedef struct __THD_DATA
{
TICKET* pTicket;
char strThreadName[NAME_LINE];
__THD_DATA() : pTicket(NULL)
{
memset(strThreadName, 0, NAME_LINE * sizeof(char));
}
}THD_DATA;
//基本類型數據轉換成字符串
template<class T>
string convertToString(const T val)
{
string s;
stringstream ss;
ss << val;
ss >> s;
return s;
}
//售票程序
DWORD WINAPI SaleTicket(LPVOID lpParameter)
{
THD_DATA* pThreadData = (THD_DATA*)lpParameter;
TICKET* pSaleData = pThreadData->pTicket;
while(pSaleData->nCount > 0)
{
//請求獲得一個互斥量鎖
WaitForSingleObject(g_hEvent,INFINITE);
WaitForSingleObject(g_hMutex, INFINITE);
if (pSaleData->nCount > 0)
{
cout << pThreadData->strThreadName << "出售第" << pSaleData->nCount -- << "的票,";
if (pSaleData->nCount >= 0) {
cout << "出票成功!剩餘" << pSaleData->nCount << "張票." << endl;
} else {
cout << "出票失敗!該票已售完。" << endl;
}
}
//Sleep(10);
//釋放互斥量鎖
ReleaseMutex(g_hMutex);
}
return 0L;
}
//售票系統
int _tmain(int argc, _TCHAR* argv[])
{
//創建一個互斥量
g_hMutex = CreateMutex(NULL, FALSE, NULL);
//創建一個控制量
g_hEvent = CreateEvent(NULL,TRUE,TRUE,NULL);
//初始化火車票
TICKET ticket;
ticket.nCount = 100;
strcpy(ticket.strTicketName, "北京-->贛州");
//定義售票口數組 和 線程數組
const int THREAD_NUMM = 8;
THD_DATA threadSale[THREAD_NUMM];
HANDLE hThread[THREAD_NUMM];
//循環售票
for(int i = 0; i < THREAD_NUMM; ++ i)
{
threadSale[i].pTicket = &ticket;
string strThreadName = convertToString(i);
strThreadName = "窗口" + strThreadName;
strcpy(threadSale[i].strThreadName, strThreadName.c_str());
//創建線程
hThread[i] = CreateThread(NULL, NULL, SaleTicket, &threadSale[i], 0, NULL);
//請求獲得一個互斥量鎖
if (i==0)
{
WaitForSingleObject(g_hMutex, 10);
cout << "開始出售 " << threadSale[i].pTicket->strTicketName << " 的票..." << endl;
//釋放互斥量鎖
ReleaseMutex(g_hMutex);
}
if (i==6)
{
SetEvent(g_hEvent);
//PulseEvent(g_hEvent);
//ResetEvent(g_hEvent);
}
//關閉線程
CloseHandle(hThread[i]);
}
//CloseHandle(g_hEvent);
system("pause");
return 0;
}