Win32多线程程序设计(三)
多线程等待的艺术-等待一个线程的结束
前言:
等待某件事情的发生是线程经常要做的事情,当你读取用户的输入,或是存取磁盘文件时,线程必须等待,当结束主线程之前,我们要保证这个进程的所有用户线程已经结束-如何等待其他线程的结束-是一个值得深思的问题
之前我们使用GetExitCodeThread()判断一个线程是否还在执行,通过不断的检查GetExitCodeThread()的返回值,判断某个线程是否结束,只有线程真正结束时,我们才结束主线程,如果我们没有等待线程结束就莽撞的结束主线程,可能线程会在完成它的工作之前就被强制的结束掉,造成不可预料的后果
通过GetExitCodeThread()等待是一种等待方式,优点和缺点都很明显,我们可以接受一个或两个线程以这种方式等待,但如果有几百个线程都是靠不断的调用GetExitCodeThread()等待结束,就会白白浪费很多CPU时间,如何让等待变的有效率,是多线程程序中迫需解决的一个问题
1.常用的两个等待技术
1) Sleep()
我们可以使用Win32 Sleep()让线程休眠一个指定的时间,直到渡过这个指定时间后才恢复,这种等待方式实现简单并且可以在不减少CPU的利用率的条件下进行等待,但这种方法有一个缺陷,在抢占式操作系统中我们不可能真正知道一个线程到底要执行多久,有时候即使一个本可以快速完成的线程,如果有另一个更高优先级的线程正在执行的话,那么这个线程会需要更长的时间,这会导致线程在未完成其工作的情况下被强制结束
2) busy loop
busy loop也称为busy waits,通常通过不断的调用GetExitCodeThread()判断线程执行状态,直到其结果不再是STILL_ACTIVE等待结束,它最大的缺点是浪费CPU时间,CPU的利用率很低,busy loop会对系统效率造成很大的冲击,我们通常在程序中避免使用busy loop
2.Windows系统效率测试与比较的方法
当我们实现一个功能时,常常会有不同的解决方案,这时我们就会需要评估那个方案更优,执行时间是很重要的一个评估指标,我们通过比较功能相同的代码的执行时间,从而选出时间最短的解决方案,计算执行时间的代码段如下:
int main()
{
DWORD begin;
DWORD elapsed;
begin = GetTickCount();
…
//要计算执行时间的代码段
…
elapsed = GetTickCount() – begin;
return 0;
}
注:
在抢先式多任务系统下,操作系统没有能力分辨那个线程的工作是有用的,那个线程的工作是没用的,每个线程一律获得平等的CPU时间,当我们使用busy loop时,主线程利用获得的CPU时间,疯狂的检查GetExitCodeThread()返回值,其实全是在做无用功,busy loop从其他真正需要CPU时间的程序中取走宝贵的资源,对程序来说,是一种灾难!
有时,即使我们没有使用线程,也会无意识的写出比busy loop更低级的代码,下面这段代码用掉所有可用的CPU时间,纯粹的只是等待
void Wait(DWORD ms)
{
DWORD begin = GetTickCount();
while(GetTickCount – begin < ms)
; //Do Nothing
}
注:
busy loop在多线程和单线程都会发生,多线程中是指通过GetExitCodeThread()不断判断,等待线程结束,单线程中是指一段不执行有效计算的循环体
性能监视器:
Windows给我们提供了一个强大的工具,性能监视器,可以用来检测系统的性能
1) 打开方式:
单击”控制面板-管理工具-性能”即可打开性能监视器
2) 功能:
性能监视器可以告诉我们CPU的忙碌程度,以确定我们所写的程序有预期的行为,当启动一个程序后,如果CPU利用率未达到100%时,表示CPU运算能力还有余裕可以发挥,如果到达100%,CPU就得开始分割时间给所有线程共享了,如果CPU利用率在100%处停留很久,说明这个程序可能有一个busy loop
3.通过WaitForSingleObject()等待一个线程(更高级的Sleep())
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds)
参数:
hHandle 要等待的核心对象的handle(这里是一个线程核心对象)
dwMillliseconds 等待的最长时间,时间终了,即使handle尚未成为激发状态,此函数还是返回,此值可以是0(代表立刻返回),也可以是INFINITE代表无穷等待
返回值:
如果函数失败,则返回WAIT_FAILED
函数执行成功的可能因素有:
1) 等待的核心对象变为激发状态,此时返回WAIT_OBJECT_0
2) 核心对象变为激发状态之前,等待的时间终了,返回WAIT_TIMEOUT
注1:
核心对象的状态:
Win32 的各种核心对象,如文件,线程,互斥器等,这些核心对象有两种状态:激发状态及未激发状态,不同的核心对象的状态代表不同的含义,如信号量和互斥器可记录红灯绿灯状态,文件对象可告诉我们一个I/O操作何时完成,线程对象的状态告诉我们其何时结束(线程结束时变为激发状态)。线程往往会对这些核心对象的状态感兴趣,如线程#1需要等待线程#2结束,则在线程#1中调用waitForSingleObject(#2, time),当线程#2结束时,线程#1获知,这样之前的busy loop就可以被替换为更有效的代码段
busy loop:
for(;;)
{
int rc;
rc = GetExitCodeThread(#1, &exitCode);
if (!rc && exitCode != STILL_ACTIVE)
break;
}
替换为:
WaitForSingleObject(#1, INFINITE);
注1:
忙等中,操作系统不断询问被等待的对象是否变为激发状态,替换为当被等待的对象变为激发状态时通知操作系统
注2:
time-out参数在编程中的用途:
time-out有一个特别重要的用途,我们可以设定time-out为0,这样检查handle的状态后立刻返回,如果handle为激发状态则返回WAIT_OBJECT_0,否则,返回WAIT_TIMEOUT
我们也可以设定time-out为一个指定的值,避免所等待线程进入一个无穷等待,当超过指定时间值后,根据函数的返回值及time-out是否终了,设置一些警告信息
例如可以利用time-out提供一个动画,每500毫秒time-out一次,更新图示,然后继续等待
注3:
实际上,多种核心对象都可以当做WaitForSingleObject()的等待目标,系统等待这一对象”被激发”,当等待的对象变为激发状态时返回,对线程来说,当线程正在执行时,线程对象处于未激发状态,当线程结束时,线程对象变为激发状态,因此如果等待的是一个线程对象,将会在线程结束时,线程对象变为激发状态,导致WaitForSingleObject()醒来
结束语:
我们重温了busy loop的不良结果,学习了Windows性能监视器的用途,认识了核心对象激发状态和非激发状态,并如何在一个线程中等待一个核心对象被激发
WaitForSingleObject()是等待中的终极武器,他集结了所有等待的优点
1)在不占用CPU时间的情况下等待某个对象
2)当被等待的对象变为激发状态时主动通知系统,避免系统反复的查询
 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号