如今咱們回過頭來說WaitForSingleObject這個函數,從前面的例子中咱們看到WaitForSingleObject這個函數將等待一個對象變爲有信號狀態,那麼具備信號狀態的對象有哪些呢?下面是一部分:
Mutex
Event
Semaphore
Job
Process
Thread
Waitable timer
Console input
互斥量(Mutex),信號燈(Semaphore),事件(Event)均可以被跨越進程使用來進行同步數據操做,而其餘的對象與數據同步操做無關,但對於進程和線程來說,若是進程和線程在運行狀態則爲無信號狀態,在退出後爲有信號狀態。因此咱們可使用WaitForSingleObject來等待進程和線程退出。(至於信號燈,事件的用法咱們接下來會講)咱們在前面的例子中使用了WaitForMultipleObjects函數,這個函數的做用與WaitForSingleObject相似但從名字上咱們能夠看出,WaitForMultipleObjects將用於等待多個對象變爲有信號狀態,函數原型以下:
DWORD WaitForMultipleObjects(
DWORD nCount, // 等待的對象數量
CONST HANDLE *lpHandles, // 對象句柄數組指針
BOOL fWaitAll, // 等待方式,
//爲TRUE表示等待所有對象都變爲有信號狀態才返回,爲FALSE表示任何一個對象變爲有信號狀態則返回
DWORD dwMilliseconds // 超時設置,以ms爲單位,若是爲INFINITE表示無限期的等待
);
返回值意義:
WAIT_OBJECT_0 到 (WAIT_OBJECT_0 + nCount – 1):當fWaitAll爲TRUE時表示全部對象變爲有信號狀態,當fWaitAll爲FALSE時使用返回值減去WAIT_OBJECT_0獲得變爲有信號狀態的對象在數組中的下標。
WAIT_ABANDONED_0 到 (WAIT_ABANDONED_0 + nCount – 1):當fWaitAll爲TRUE時表示全部對象變爲有信號狀態,當fWaitAll爲FALSE時表示對象中有一個對象爲互斥量,該互斥量由於被關閉而成爲有信號狀態,使用返回值減去WAIT_OBJECT_0獲得變爲有信號狀態的對象在數組中的下標。
WAIT_TIMEOUT:表示超過規定時間。
前面的例子中的以下代碼表示等待三個線程都變爲有信號狀態,也就是說三個線程都結束。
HANDLE hThread[3];
CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待線程結束
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
此外,在啓動和等待進程結束一文中就利用這個功能等待進程結束。
經過互斥量咱們能夠指定資源被獨佔的方式使用,但若是有下面一種狀況經過互斥量就沒法處理,好比如今一位用戶購買了一份三個併發訪問許可的數據庫系統,你的老闆會要求你根據用戶購買的訪問許可數量來決定有多少個線程/進程能同時進行數據庫操做,這時候若是利用互斥量就沒有辦法完成這個要求,信號燈對象能夠說是一種資源計數器。對信號燈的操做僞代碼大體以下:
Semaphore sem=3;
dword threadA(void*)
{
while(sem <= 0)
{// 至關於 WaitForSingleObject
wait ...
}
// sem > 0
// lock the Semaphore
sem -- ;
do functions ...
// release Semaphore
sem ++ ;
return 0;
}
這裏信號燈有一個初始值,表示有多少進程/線程能夠進入,當信號燈的值大於0時爲有信號狀態,小於等於0時爲無信號狀態,因此能夠利用WaitForSingleObject進行等待,當WaitForSingleObject等待成功後信號燈的值會被減小1,直到釋放時信號燈會被增長1。用於信號燈操做的API函數有下面這些:
建立信號燈:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,// 安全屬性,NULL表示使用默認的安全描述
LONG lInitialCount, // 初始值
LONG lMaximumCount, // 最大值
LPCTSTR lpName // 名字
);
打開信號燈:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, // 存取方式
BOOL bInheritHandle, // 是否能被繼承
LPCTSTR lpName // 名字
);
釋放信號燈:
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 句柄
LONG lReleaseCount, // 釋放數,讓信號燈值增長數
LPLONG lpPreviousCount // 用來獲得釋放前信號燈的值,能夠爲NULL
);
關閉信號燈:
BOOL CloseHandle(
HANDLE hObject // 句柄
);
能夠看出來信號燈的使用方式和互斥量的使用方式很是類似,下面的代碼使用初始值爲2的信號燈來保證只有兩個線程能夠同時進行數據庫調用:
DWORD threadA(void* pD)
{
int iID=(int)pD;
//在內部從新打開
HANDLE hCounterIn=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44");
for(int i=0;i<3;i++)
{
printf("%d wait for object\n",iID);
WaitForSingleObject(hCounterIn,INFINITE);
printf("\t\tthread %d : do database access call\n",iID);
Sleep(100);
printf("\t\tthread %d : do database access call end\n",iID);
ReleaseSemaphore(hCounterIn,1,NULL);
}
CloseHandle(hCounterIn);
return 0;
}
//in main function
{
//建立信號燈
HANDLE hCounter=NULL;
if( (hCounter=OpenSemaphore(SEMAPHORE_ALL_ACCESS,FALSE,"sam sp 44"))==NULL)
{
//若是沒有其餘進程建立這個信號燈,則從新建立
hCounter = CreateSemaphore(NULL,2,2,"sam sp 44");
}
//建立線程
HANDLE hThread[3];
CWinThread* pT1=AfxBeginThread((AFX_THREADPROC)threadA,(void*)1);
CWinThread* pT2=AfxBeginThread((AFX_THREADPROC)threadA,(void*)2);
CWinThread* pT3=AfxBeginThread((AFX_THREADPROC)threadA,(void*)3);
hThread[0]=pT1->m_hThread;
hThread[1]=pT2->m_hThread;
hThread[2]=pT3->m_hThread;
//等待線程結束
WaitForMultipleObjects(3,hThread,TRUE,INFINITE);
//關閉句柄
CloseHandle(hCounter);
}
信號燈有時用來做爲計數器使用,通常來說將其初始值設置爲0,先調用ReleaseSemaphore來增長其計數,而後使用WaitForSingleObject來減少其計數,遺憾的是一般咱們都不能獲得信號燈的當前值,可是能夠經過設置WaitForSingleObject的等待時間爲0來檢查信號燈當前是否爲0。
接下來咱們講最後一種同步對象:事件,前面講的信號燈和互斥量能夠保證資源被正常的分配和使用,而事件是用來通知其餘進程/線程某件操做已經完成。例如:如今有三個線程:threadA,threadB,threadC,如今要求他們中的部分功能要順序執行,也就是說threadA執行完一部分後threadB執行,threadB執行完一部分後threadC開始執行。也許你以爲下面的代碼能夠知足要求:
要求:A1執行完後執行B2而後執行C3,再假設每一個任務的執行時間都爲1,並且容許併發操做。
方案一:
dword threadA(void*)
{
do something A1;
create threadB;
do something A2;
do something A3;
}
dword threadB(void*)
{
do something B1;
do something B2;
create threadC;
do something B3;
}
dword threadC(void*)
{
do something C1;
do something C2;
do something C3;
}
方案二:
dword threadA(void*)
{
do something A1;
do something A2;
do something A3;
}
dword threadB(void*)
{
do something B1;
wait for threadA end
do something B2;
do something B3;
}
dword threadC(void*)
{
do something C1;
do something C2;
wait for threadB end
do something C3;
}
main()
{
create threadA;
create threadB;
create threadC;
}
方案三:
dword threadA(void*)
{
do something A1;
release event1;
do something A2;
do something A3;
}
dword threadB(void*)
{
do something B1;
wait for envet1 be released
do something B2;
release event2;
do something B3;
}
dword threadC(void*)
{
do something C1;
do something C2;
wait for event2 be released
do something C3;
}
main()
{
create threadA;
create threadB;
create threadC;
}
比較一下三種方案的執行時間:
方案一 方案二 方案三
1 threadA threadB threadC threadA threadB threadC threadA threadB threadC
2 A1 A1 B1 C1 A1 B1 C1
3 A2 B1 A2 C2 A2 B2 C2
4 A1 B2 A3 A3 B3 C3
5 B3 C1 B2
6 C2 B3
7 C3 C3
8
能夠看出來方案三的執行時間是最短的,固然這個例子有些極端,但咱們能夠看出事件對象用於通知其餘進程/線程某件操做已經完成方面的做用是很大的,並且若是有的任務要在進程尖進行協調採用等待其餘進程中線程結束的方式是不可能實現的。此外我也但願經過這個例子講一點關於分析線程執行效率的方法。數據庫