轉自:http://blog.sina.com.cn/s/blog_52324e0b0100oitj.html
EVENT是多線程同步機制中最具彈性。它的激發與未激發狀態完全是由程序來進行控制的。問不會隨着Wait..函數而改變。它是一個內核對象。
1.創建event:
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, //安全屬性
BOOL bManualReset, //是否設置爲手動重置的對象,TRUE 手動重置, FALSE 自動重置
BOOL bInitialState, //初始是否設置爲激發狀態
LPTSTR lpName //名稱
); (更多,參考MSDN)
根據這個創建的函數,event可以分爲兩種,手動重置對象和自動重置對象。
1.手動重置對象:對象的激發與未激發狀態都需要程序中調用相應的函數進行設置。wait..函數並不能改變對象的狀態。設置對象未激發狀態可以調用setevent和pulseevent,設置對象爲未激發狀態可以調用resetevent.當手動重置事件變爲激發狀態,那麼等待該對象的所有線程都將變成可調度線程。下面是一個手動重置對象的列子:
#include <iostream>
#include <cstdlib>
using namespace std;
#include <Windows.h>
//線程函數
DWORD WINAPI ThreadProc(LPVOID param);
//全局事件對象
HANDLE gEvent;
int main()
{
//創建手動重置對象,初始爲未激發狀態
gEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (gEvent == NULL)
{
cout<<"Create event failed"<<endl;
return 0;
}
//創建三個線程
HANDLE threadHandles[3];
DWORD threadId;
for (int i = 0; i < 3; ++i)
{
threadHandles[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)i, 0, &threadId);
if (threadHandles[i] == NULL)
{
cout<<"Create thread failed."<<endl;
return 0;
}
}
SetEvent(gEvent);
WaitForMultipleObjects(3, threadHandles, TRUE, INFINITE);
for (int i = 0; i < 3; ++i)
{
CloseHandle(threadHandles[i]);
}
CloseHandle(gEvent);
system("pause");
return 0;
}
DWORD WINAPI ThreadProc(LPVOID param)
{
cout<<"Thread "<<(int)param<<" wait for event."<<endl;
WaitForSingleObject(gEvent, INFINITE);
ResetEvent(gEvent);
cout<<"Thread "<<(int)param<<" worked."<<endl;
SetEvent(gEvent);
return 0;
}
注意:對於手動重置對象,由於其狀態是由程序來進行控制的。所以調用wait..函數不能改變對象的狀態。所以如果在WaitForSingleObject(gEvent, INFINITE);ResetEvent(gEvent);發生上下文切換,那麼多個線程之間的操作可能就會出現混亂。
2.自動重置對象:當一個線程等待對象成功以後,會自動的將這個對象設置爲未激發狀態。這樣就可以避免上下文切換引起的多個線程可以同時進入事件對象保護的代碼中。一個自動重置事件對象的例子:
//線程函數
DWORD WINAPI ThreadProc(LPVOID param);
//全局事件對象
HANDLE gEvent;
int main()
{
//創建手動重置對象,初始爲未激發狀態
gEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (gEvent == NULL)
{
cout<<"Create event failed"<<endl;
return 0;
}
//創建三個線程
HANDLE threadHandles[3];
DWORD threadId;
for (int i = 0; i < 3; ++i)
{
threadHandles[i] = CreateThread(NULL, 0, ThreadProc, (LPVOID)i, 0, &threadId);
if (threadHandles[i] == NULL)
{
cout<<"Create thread failed."<<endl;
return 0;
}
}
SetEvent(gEvent);
WaitForMultipleObjects(3, threadHandles, TRUE, INFINITE);
for (int i = 0; i < 3; ++i)
{
CloseHandle(threadHandles[i]);
}
CloseHandle(gEvent);
system("pause");
return 0;
}
DWORD WINAPI ThreadProc(LPVOID param)
{
cout<<"Thread "<<(int)param<<" wait for event."<<endl;
WaitForSingleObject(gEvent, INFINITE);
cout<<"Thread "<<(int)param<<" worked."<<endl;
SetEvent(gEvent);
return 0;
}
幾點比較雜的東西:
關於PulseEvent,WIN32多線程程序設計中關於它的說明:
如果是一個手動重置對象,它會把對象設置爲激發狀態,喚醒"所有"等待中的線程,然後把event恢復爲非激發狀態。如果是一個自動重置對象,它會把對象設爲激發狀態,喚醒"一個"等待中的線程,然後把對象設置爲未激發狀態。
由於存在狀態切換,如果當這種狀態切換的時候沒有任何的線程等待該對象的時候(可能是真的沒有線程等待,或者是上下文切換),那麼這個要求線程甦醒變成可調度的請求就會失敗。
windows核心編程中關於PulseEvent的說明:
PulseEvent函數使得事件變爲已通知狀態,然後立即又變爲未通知狀態,這就像在調用setevent後又立即調用resetevent函數一樣。如果在人工重置的事件上調用PulseEvent函數,那麼在發出該事件時,等待該事件的任何一個線程或所有線程將變爲可調度線程。如果在自動重置事件上調用PulseEvent函數,那麼只有一個等待該事件的線程變爲可調度線程。如果在發出事件時沒有任何線程在等待該事件,那麼將不起任何作用。
PulseEvent函數並不非常有用。實際上我在自己的應用程序中從未使用它,因爲根本不知道什麼線程將會看到事件的發出並變成可調度線程。由於在調用PulseEvent時無法知道任何線程的狀態,因此該函數並不那麼有用。我相信在有些情況下,雖然PulseEvent函數可以方便地供你使用,但是你根本想不起要去使用它。
最後關於手動重置以及自動重置的幾點說明:
手動重置,可以當做一個開關。當你進行完必要的初始化之後,就可以打開這個開關,然後讓所有的處理的線程進行處理(當然進行應該的應該都是隻讀的處理)。自動重置對象可以像互斥對象一樣進行排他性的保護,但是比起互斥對象它更加的靈活。