Win32多线程程序设计学习(第三章)

2022/1/6 20:04:37

本文主要是介绍Win32多线程程序设计学习(第三章),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

快跑与等待

 在这一章中我们重温了 busy loops 的不良结果,并且学习如何使用Windows NT 的性能监视器捕捉其中的问题。我们也认识了所谓的“激发状态的对象”,并且学习如何在一个 worker 线程或一个 GUI 线程中等待一个或多个这样的对象。最后,我们看到了如何重建一个主消息循环,俾能够适当地使用 MsgWaitForMultipleObjects() 。

1. 看似闲暇却忙碌(Busy Waiting)

文章中的例子:计算圆周率 PI

第一种情况:直接调用计算圆周率的函数。

第二种情况:开启一个线程运行计算圆周率的函数,并且主线程一直while循环等待worker线程结束。

结果:第二种消耗的时间是第一种的两倍。

原因:是抢先式多任务造成的影响。操作系统没有办法判断哪个线程是有用的,哪个线程是没用的,所以每个线程都获取同等的CPU时间。

2,性能监视器(Performance Monitor)

3,等待一个线程的结束

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。Mutexes 将在第4章有更多讨论。

 获得一个线程对象的 handle 之后,WaitForSingleObject() 要求操作系统 让线程 #1 睡觉,直到以下任何一种情况发生:

i 线程 #2 结束

i dwMilliseconds 时间终了。

该值系从函数调用后开始计算。 由于操作系统持续追踪线程 #2 ,即使线程 #2 失事或被强迫结束, WaitForSingleObject() 仍然能够正常运作。

 我如何得知一 个核心对象是否处于激发状态?

设定 time-out 为 0,使你能够检查 handle 的状态并立刻返回,没有片刻停留。如果 handle 已经备妥,那么这个函数会成功并传回 WAIT_OBJECT_0 。否则,这个函数 立刻返回并传回 WAIT_TIMEOUT 。

4,被激发的对象(Signaled Objects) 

1) 什么是一个被 激发的对象?

        1> 核心对象有两种状态:激发与未激发

        2> 当核心对象被激发时,会导致 WaitForSingleObject() 醒来。稍后你将看到的 其他 Wait()         函数也是如此

例如:当线程正在执行时,线程对象处于未激发状态。当线程结束时,线程对象就被激发                 了。

2) “激发” 对于不同的核 心对象有什么 不同的意义?(表格来自《win32》)

 5,等待多个对象

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() 的返回值有些复杂。

i 如果因时间终了而返回,则返回值是 WAIT_TIMEOUT ,类似 WaitForSingleObject()。

i 如果 bWaitAll 是 TRUE ,那么返回值将是 WAIT_OBJECT_0 。

i 如果 bWaitAll 是 FALSE ,那么将返回值减去 WAIT_OBJECT_0 ,就 表示数组中的哪一个 handle 被激发了。

i 如果你等待的对象中有任何 mu texes ,那么返回值可能从 WAIT_ABANDONED_0 到 WAIT_ABANDONED_0 + nCount - 1。

i 如果函数失败,它会传回 WAIT_FAILED 。这时候你可以使用 GetLastError() 找出失败的原因。 

注意, handles 数组中的元素个数有上限,绝对不能够超过 MAXIMUM_WAIT_OBJECTS。在 Windows NT 3.x 和 4.0 中,其值为 64(译 注:在 Window s 95 中也一样)。 

DWORD WINAPI P3_2_ThreadFunc(LPVOID n);

#define  THREAD_POOL_SIZE 3
#define MAX_THREAD_INDEX THREAD_POOL_SIZE-1 
#define NUM_TASKS 6

void P3_2_TaskQueSFunc()
{
	HANDLE	hThrds[THREAD_POOL_SIZE];
	DWORD	threadID;
	DWORD	exitCode;
	int			iSlot = 0;
	int			rc = 0;

	for (int i = 0; i < NUM_TASKS; i++)
	{
		if (i > MAX_THREAD_INDEX)
		{
			rc = WaitForMultipleObjects(THREAD_POOL_SIZE,
				hThrds,
				FALSE,
				INFINITE);
			iSlot = rc - WAIT_OBJECT_0;
			MTVERIFY(iSlot >= 0
				&& iSlot < THREAD_POOL_SIZE);
			printf("Slot %d terminated\n", iSlot);
			MTVERIFY(CloseHandle(hThrds[iSlot]));
		}
		MTVERIFY(hThrds[iSlot++] = CreateThread(NULL,
			0,
			P3_2_ThreadFunc,
			(LPVOID)iSlot,
			0,
			&threadID));
	}

	MTVERIFY(rc = WaitForMultipleObjects(THREAD_POOL_SIZE,
		hThrds,
		TRUE,
		INFINITE));
	MTVERIFY(rc >= WAIT_OBJECT_0
		&& rc < WAIT_OBJECT_0 + THREAD_POOL_SIZE);

	for (iSlot = 0; iSlot < THREAD_POOL_SIZE; iSlot++)
	{
		MTVERIFY(CloseHandle(hThrds[iSlot]));
	}
	printf("All Thread Terminated\n");
}

DWORD WINAPI  P3_2_ThreadFunc(LPVOID n)
{
	srand(GetTickCount());	//从srand (seed)中指定的seed开始,返回一个[seed, RAND_MAX(0x7fff))间的随机整数。
	Sleep((rand() % 10) * 800 + 500);
	printf("Slot %d idle\n", n);
	return (DWORD)n;
}

6,在一个 GUI 程序中等待

1)我如何在主线 程中等待一个 handle?

Windows 程序中的标准消息循环看起来像这个样子:

while (GetMessage(&msg, NULL, 0, 0,))
{
 TranslateMessage(&msg);
 DispatchMessage(&msg);
} 

GetMessage() 有点像是特殊版本的 WaitForSingleObject(),它等待消息而不是核心对象。一旦你调用 GetMessage(),除非有一个消息真正进入你的消息队列(message queue )之中,否则它不会返回。在此期间,Windows 就可以 自由地将 CPU 时间给予其他程序

问题:问题是,如果你正使用 WaitForSingleO bject()或 WaitForMultipleObjects() 等待某个对象被激发,你根本没有办法回到主消息循环中去。这个时候,窗口会停止重绘,你的程序菜单就不再有作用,用户不喜欢的事情则慢慢开始发生。

下面这种做法并不能真正解决问题:

当主线程正在处理主消息循环时,不 使用第二个线程来等待 handles。因为如果你这么做,只是把问题从某处移到 另一处,你还是得决定到底要使用 " polling" 方法,或是使用一个 Win32 Wait...() 函数,来察知新线程的结束。

2)MsgWaitForMultipleObjects() 函数

这个函 数非常类似 WaitForMultipleObjects(),但它会在“对象被激发”或“消息到达队列”时被唤醒而返回。MsgWaitForMultipleObjects() 多接受一个参数,允许 指定哪些消息是观察对象。

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。

代码:

 

 有数种情况是这个循环必须处理而却可能在它第一次设计时容易被忽略的:

1. 在你收到 WM_ QUIT 之后,Windows 仍然会传送消息给你。如果你要在收到WM_QUIT 之后等待所有线程结束,你必须继续处理你的消息,否则窗口会变得反应迟 钝,而且没有重绘能力。

2. MsgWaitForMultipleObjects() 不允许 handles 数组中有缝隙产生。所以 当某个 handle 被激发了时,你应该在下一次调用 MsgWaitForMultipleObjects() 之前先把 handles 数组做个整理、紧压,不要只是把数组中的 handle 设为 NULL。如果你仔细观察上述程序代码,你会发现我把最尾端的 handle 移到 腾空的位置,然后再把数组大小减 1。

3. 如果有另一个线程更改了对象数组,而那是你正在等待的,那么你需要一种方法,可以强迫 MsgWaitForMultipleObjects() 返回,并重新开始,以 包含这个新的 handle 。列表 3-5 的解决之道是利用 WM_ THREADCOUNT 消 息。

 

 

 



这篇关于Win32多线程程序设计学习(第三章)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程