第三章   快跑和等待

资源网络收集 感谢原创者

转自http://blog.sina.com.cn/s/blog_5678943c0100d4po.html

本章回答了如下几个问题:

   如何等待线程序的结束?为什么通过查询方式判断线程是否终止是“看似空闲实则忙碌”?这有何不妥?怎样改进,才能使等待变得有效率起来?

   如何等待一个线程的结束?如何等待多个线程的结束?推而广之,如何等待一个核心对象?如何等待多个核心对象?

   GDI线程中等待需要特别注意些什么?怎么做?对于主线程来说,收到WM_QUIT消息时能否立即终止线程?为什么?该怎么做?

   在多对象等待中,对象数组的处理需要注意些什么?比如,正在等待的某个对象被其它线程删除了、修改了,或要新添一个需要等待的核心对象,怎么处理? 

看似闲暇却忙碌(Busy Waiting

操作系统没有能力分辨哪个线程的工作是有用的,哪个线程的工作是比较没用的,所以每个线程获得一律平等的CPU时间。通过反复调用GetExitCodeThread()查看线程是否终结会白白浪费CPU时间。

记住:让等待变得有效率,是一件很重要的事。

 

等待一个线程的结束

DWORD WaitForSingleObject(

HANDLE hHandle,        // handle to object to wait for

DWORD dwMilliseconds   // time-out interval in milliseconds

);

此函数返回成功的三个因素:

1  等待的目标(核心对象)变成激发状态,返回WAIT_OBJECT_0

2  核心对象变成激发状态前,等待时间到了,返回WAIT_TIMEOUT

3  如果一个拥有MUTEX(互斥器)的线程结束前没有释放MUTEX,则传回WAIT_ABANDONED 

如何检查核心对象是否处于被激发状态?

WaitForSingleObject()中的时间超时设为0,如果对象处于激发状态,立即返回WAIT_OBJECT_0,否则返回WAIT_TIMEOUT 

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

当核心对象被激发时,会导致WaitForSingleObject()醒来。其它Wait()函数也是如此。

需要注意的是:醒来未必立即被调动,而只是从休眠态队列排入就绪队列,等待操作系统调度。 

对于不同的核心对象,是否被激发含义不一:

对于线程/进程,如果还在运行,该对象就处于未激发;如果运行结束,则转入激发状态。

对于文件对象,如果一个I/O操作完成,该文件对象即处于激发状态;

对于事件,CreateEvent()初态为TRUE,或SetEvent()、PulseEvent(),事件为激发态;

对于互斥器,如果未被任何进程锁定,该对象处于激发状态;

对于信号量,如果信号量的现值大于0,该对象处于激发状态;

对于磁盘改变通知,当一个特定的磁盘子目录中发生了一件特别的改变时,此对象被激发。

对于控制台输入,当Console窗口的输入缓冲区中有数据可用时,此对象被激发。 

数个线程可以同时等待相同线程handle,当该线程handle变成激发状态时,所有等待的线程都会被唤醒。然而,其它核心对象可能只唤醒一个等待中的线程。到底是哪一种行为,得视你等待的是何种对象。

有些对象的激发状态只能维持到一个等待中的线程被唤醒,有些象的激发状态则能维持到它被明白地Reset了。(比较两种事件对象:自动事件属于前一类别,手工事件则属于后一种) 

Note:资源未被占有,即为激发状态

关于目录监控

我们可以检测一个特定目录发生的变化,具体可使用下面的三个函数:

HANDLE FindFirstChangeNotification(

LPCTSTR lpPathName,    // pointer to name of directory to watch

BOOL bWatchSubtree,    // flag for monitoring directory or directory tree

DWORD dwNotifyFilter   // filter conditions to watch for

);

lpPathName定义被监视的目录所在;

bWatchSubtree定义是否监视该目录下的子目录;

dwNotifyFilter定义具体监视哪些变化,可以是如下六种变化(可多选):

FILE_NOTIFY_CHANGE_FILE_NAME :建立、删除、文件改名

FILE_NOTIFY_CHANGE_DIR_NAME :建立或删除一个目录

FILE_NOTIFY_CHANGE_ATTRIBUTES :目录及子目录中的任何属性改变

FILE_NOTIFY_CHANGE_SIZE :目录及子目录中的任何文件大小的改变

FILE_NOTIFY_CHANGE_LAST_WRITE :目录及子目录中的任何文件的最后写入时间的改变

FILE_NOTIFY_CHANGE_SECURITY :目录及子目录中的任何属性改变

FindFirstChangeNotification()函数的返回值可以作为等待函数中的等待对象,从而实现对特定目录或子目录的监视。只要被监视的某一种事件发生,等待函数返回。

等待函数返回后,应用程序可以对此进行响应处理,调用FindNextChangeNotification()并继续等待。 

BOOL FindNextChangeNotification(

HANDLE hChangeHandle   // handle to change notification to signal

);

FindNextChangeNotification()函数成功返回后,应用程序可以使用等待函数等待变化通知。

如果一个变化在FindFirstChangeNotification()调用后FindNextChangeNotification()调用前发生,操作系统记录这个改变,等FindNextChangeNotification()执行完,记录改变立即激活一个等待它的等待函数。

没使用等待函数前FindNextChangeNotification()不可使用超过1次。如果有改变等待处理,调用FindNextChangeNotification()可能会漏失一个改变通知。

如果要结束目录监视,使用FindCloseChangeNotification()。 

BOOL FindCloseChangeNotification(

HANDLE hChangeHandle   // handle to change notification to close

); 

等待多个对象

Win32中可以等待多个对象,等待所有事件或者某一个事件被激发。

DWORD WaitForMultipleObjects(

DWORD nCount,             // number of handles in the handle array

CONST HANDLE *lpHandles,  // pointer to the object-handle array

BOOL fWaitAll,            // wait flag

DWORD dwMilliseconds      // time-out interval in milliseconds

); 

在一个GDI程序中等待

如果需要在主消息循环里等待某个对象,如果只是使用WaitForSingleObjects()或WaitForMultipleObjects(),你根本就无法回到主消息循环里。这样,应用程序的用户界面停止重绘,菜单工具条无法响应,总之,糟糕透了。

为了同时等待对象,并等待消息,可使用下面这个函数:

DWORD MsgWaitForMultipleObjects(

DWORD nCount,          // number of handles in the handle array

LPHANDLE pHandles,     // pointer to the object-handle array

BOOL fWaitAll,         // wait for all or wait for one

DWORD dwMilliseconds,  // time-out interval in milliseconds

DWORD dwWakeMask       // type of input events to wait for

);

前几个参数和WaitForMultipleObjects()类似,最后一个参数定义欲观察的用户输入消息。函数返回值为WAIT_OBJECT_0 + nCount时表示消息到达队列

 

消息处理的一个范本:

int rc = MsgWaitForMultipleObjects(nWaitCount,hWaitArray,FALSE,INFINITE,QS_ALLINPUT);

if (rc == WAIT_OBJECT_0 + nWaitCount)

{

while (PeekMessage(&msg,NULL,0,0,PM_REMOVE)

{

if (msg.message == WM_QUIT)

{

quit = true;

exitcode = msg.wParam;

break;

}

TranslateMessage(&msg);

DispatchMessage(&msg);

}

else

{

……

}


使用MsgWaitForMultipleObjects()时需要注意的几个地方

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

2MsgWaitForMultipleObjects()不允许handles数组中有缝隙产生。所以,如果某个对象被激发了后,你应该在下一次调用MsgWaitForMultipleObjects()前把handles数组重新整理、紧压,而不是把该位置设置为NULL了事。

3.如果有另一个线程更改了对象数组,而那是你正等待的,那么你需要一种方法,强迫MsgWaitForMultipleObjects()返回,并重新开始,以包含这个新的 handle