3.1 busy waiting
使用 GetExitCodeThread() 可以决定一个线程是否还在执行。只要持续不断地检查 GetExitCodeThread() 的返回值,就可以等待某个线程结束。如果你没有等待线程结束就莽撞地结束程序,线程会被系统强制结束掉——在它完成它的工作之前。
如果只有一或两个线程以这种方式等待,倒也还好。但如果你有两百个线程都是靠不断地调用 GetExitCodeThread() 来等待结束呢?突然之间你会发现,CPU 的用途似乎都没有花在刀口上。
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
#include "MtVerify.h"
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThrd;
DWORD exitCode = 0;
DWORD threadId;
DWORD begin;
DWORD elapsed;
puts("Timing normal function call...");
begin = GetTickCount();
ThreadFunc(0);
elapsed = GetTickCount()-begin;
printf("Function call took: %d.%.03d seconds\n\n", elapsed/1000, elapsed%1000);
puts("Timing thread + busy loop...");
begin = GetTickCount();
MTVERIFY( hThrd = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)1,
0,
&threadId )
);
/* This busy loop chews up lots of CPU time */
for (;;)
{
GetExitCodeThread(hThrd, &exitCode);
if ( exitCode != STILL_ACTIVE )
break;
}
elapsed = GetTickCount()-begin;
printf("Thread + busy loop took: %d.%.03d seconds\n", elapsed/1000, elapsed%1000);
MTVERIFY( CloseHandle(hThrd) );
return EXIT_SUCCESS;
}
DWORD WINAPI ThreadFunc(LPVOID n)
{
int i;
int inside = 0;
double val;
UNREFERENCED_PARAMETER(n);
srand( (unsigned)time( NULL ) );
for (i=0; i<1000000; i++)
{
double x = (double)(rand())/RAND_MAX;
double y = (double)(rand())/RAND_MAX;
if ( (x*x + y*y) <= 1.0 )
inside++;
}
val = (double)inside / i;
printf("PI = %.4g\n", val*4);
return 0;
}
执行结果如下:
Timing normal function call...
PI = 3.139
Function call took: 0.063 seconds
Timing thread + busy loop...
PI = 3.139
Thread + busy loop took: 0.078 seconds
请按任意键继续. . .
操作系统没有能力分辨哪个线程的工作是有用的,哪个线程的工作是比较没有用的,所以每个线程获得一律平等的 CPU 时间。本例的主线程使用其所获得的 CPU 时间,疯狂地检查GetExitCodeThread() 的传回值,每秒数百万次。吓,一点生产力都没有!
3.2 等待一个线程结束
DWORD WaitForSingleObject(
HANDLE hHandle,
DWORD dwMilliseconds
);
参数:
hHandle:等待对象的 handle (代表一个核心对象)。在本例中,此为线程 handle 。
dwMilliseconds:等待的最长时间。时间终了,即使 handle 尚未成为激发状态,此函数还是要返回。此值可以是 0(代表立刻返回),也可以是INFINITE 代表无穷等待
返回值:
如果函数失败,则传回WAIT_FAILED。这时候你可调用 GetLastError() 取
得更多信息。此函数的成功有三个因素:
1. 等待的目标(核心对象)变成激发状态。这种情况下返回值将为WAIT_OBJECT_0。
2. 核心对象变成激发状态之前,等待时间终了。这种情况下返回值将为WAIT_TIMEOUT。
3. 如果一个拥有 mutex(互斥器)的线程结束前没有释放 mutex,则传回 WAIT_ABANDONED。
三个线程,没一个线程随机等待一段时间结束
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "MtVerify.h"
DWORD WINAPI ThreadFunc(LPVOID);
#define THREAD_POOL_SIZE 3
int main()
{
HANDLE hThrds[THREAD_POOL_SIZE];
DWORD threadId;
int i;
DWORD exitCode;
for (i=0; i<THREAD_POOL_SIZE; i++)
{
MTVERIFY( hThrds[i] = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)i,
0,
&threadId ) );
printf("Launched thread #%d\n", i);
}
for (i=0; i<THREAD_POOL_SIZE; i++)
{
WaitForSingleObject(hThrds[i], INFINITE);
MTVERIFY( GetExitCodeThread(hThrds[i], &exitCode) );
printf("Slot %d terminated\n", exitCode );
MTVERIFY( CloseHandle(hThrds[i]) );
}
printf("All slots terminated\n");
return EXIT_SUCCESS;
}
DWORD WINAPI ThreadFunc(LPVOID n)
{
srand( GetTickCount() );
Sleep((rand()%8)*500+500);
printf("Slot %d idle\n", n);
return ((DWORD)n);
}
执行结果如下:
Launched thread #0
Launched thread #1
Launched thread #2
Slot 0 idle
Slot 0 terminated
Slot 1 idle
Slot 2 idle
Slot 1 terminated
Slot 2 terminated
All slots terminated
请按任意键继续. . .
有个严重的问题就是效率低,因为它假设线程的结束次序和被产生的次序相同,实际却不是。
3.3 等待多个线程
WaitForMultipleObjects() 允许你在同一时间等待一个以上的对象。可以解决上述问题。
DWORD WaitForMultipleObjects(
DWORD nCount,
CONST HANDLE *lpHandles,
BOOL bWaitAll,
DWORD dwMilliseconds
);
参数:
nCount 表示 lpH andles 所指之 handles 数组的元素个数。最大容量是 MAXIMUM_WAIT_OBJECTS 。
lpHandles 指向一个由对象 handles 所组成的数组。这些handles 不需要为相同的类型。
bWaitAll 如果此为 TRUE ,表示所有的 handles 都必须激发,此函数才得以返回。否则此函数将在任何一个handle 激发时就返回。
dwMilliseconds 当该时间长度终了时,即使没有任何 handles 激发,此函数也会返回。此值可为 0,以便测试。亦可指定为 INFINITE ,表示无穷等待。
返回值:
WaitForMultipleObjects() 的返回值有些复杂。
1. 如果因时间终了而返回,则返回值是 WAIT_TIMEOUT ,类似WaitForSingleObject()。
2. 如果 bWaitAll 是 TRUE ,那么返回值将是 WAIT_OBJECT_0 。
3. 如果 bWaitAll 是 FALSE,那么将返回值减去 WAIT_OBJECT_0,就表示数组中的哪一个 handle 被激发了。
4. 如果你等待的对象中有任何 mutexes,那么返回值可能从WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1。
5. 如果函数失败,它会传回WAIT_FAILED。这时候你可以使用GetLastError() 找出失败的原因。
以 WaitForMultipleObjects 取 代WaitForSingleObject(),并指定 bWaitAll 参数为 FALSE 。
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
#include "MtVerify.h"
DWORD WINAPI ThreadFunc(LPVOID);
int main()
{
HANDLE hThreads[2];
DWORD dwThreadId;
DWORD dwExitCode = 0;
int i;
for (i=0; i<2; i++)
MTVERIFY( hThreads[i] = CreateThread(NULL,
0,
ThreadFunc,
(LPVOID)i,
0,
&dwThreadId )
);
Sleep(10000);
WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);
for (i=0; i<2; i++)
MTVERIFY( CloseHandle(hThreads[i]) );
printf("All terminated\n");
return EXIT_SUCCESS;
}
DWORD WINAPI ThreadFunc(LPVOID n)
{
srand( GetTickCount() );
Sleep((rand()%8)*500+500);
printf("Slot %d idle\n", n);
return ((DWORD)n);
}
执行结果如下:
Slot 1 idle
Slot 0 idle
All terminated
请按任意键继续. . .
3.4 GUI中等待
“常常回到主消息循环”是十分重要的一件事。如果你没这么做,你的窗口就会停止重绘,你的程序菜单就不再有作用,用户不喜欢的事情则慢慢开始发生。问题是,如果你正使用 WaitForSingleOject()或 WaitForMultipleObjects()等待某个对象被激发,你根本没有办法回到主消息循环中去。
DWORD MsgWaitForMultipleObjects(
DWORD nCount,
LPHANDLE pHandles,
BOOL fWaitAll,
DWORD dwMilliseconds,
DWORD dwWakeMask
);
参数:
dwWakeMask 欲观察的用户输入消息,可以是:QS_ALLINPUT、QS_HOTKEY、QS_INPUT、QS_KEY、QS_MOUSE、QS_MOUSEBUTTON、QS_MOUSEMOVE、QS_PAINT、QS_POSTMESSAGE、QS_SENDMESSAGE、QS_TIMER
返回值:
和 WaitForMultipleObjects()相比较,MsgWaitForMultipleObjects() 有一些额外的返回值意义。为了表示“消息到达队列”,返回值将是WAIT_OBJECT_0+nCount。
GetMessage会一直等待消息,知道有才返回,而PeekMessage则是查询不论有没有都会返回,而用MsgWaitForMultipleObjects可以先判断有没有消息,有消息再用PeekMessage。
while (GetMessage(&msg, NULL, 0, 0,))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
while (!quit || gNumPrinting > 0)
{ // Wait for next message or object being signaled
DWORD dwWake;
dwWake = MsgWaitForMultipleObjects(
gNumPrinting,
gPrintJobs,
FALSE,
INFINITE,
QS_ALLEVENTS);
if (dwWake >= WAIT_OBJECT_0 && dwWake < WAIT_OBJECT_0 + gNumPrinting)
{
int index = dwWake - WAIT_OBJECT_0;
gPrintJobs[index] = gPrintJobs[gNumPrinting-1];
gPrintJobs[gNumPrinting-1] = 0;
gNumPrinting--;
SendMessage(hDlgMain, WM_THREADCOUNT, gNumPrinting, 0L);
}
else if (dwWake == WAIT_OBJECT_0 + gNumPrinting)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(hDlgMain == NULL || !IsDialogMessage(hDlgMain,&msg))
{
if (msg.message == WM_QUIT)
{
quit = TRUE;
exitCode = msg.wParam;
break;
} // end if
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} // end while
}
}