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)当被等待的对象变为激发状态时主动通知系统,避免系统反复的查询

posted @ 2012-11-13 16:44  liuhao2638  阅读(793)  评论(0)    收藏  举报