第三章 快跑和等待
资源网络收集 感谢原创者
转自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之后等待所有线程的结束,你必须继续处理你的消息,否则窗口会变得反应迟钝,丧失重绘能力。
2.MsgWaitForMultipleObjects()不允许handles数组中有缝隙产生。所以,如果某个对象被激发了后,你应该在下一次调用MsgWaitForMultipleObjects()前把handles数组重新整理、紧压,而不是把该位置设置为NULL了事。
3.如果有另一个线程更改了对象数组,而那是你正等待的,那么你需要一种方法,强迫MsgWaitForMultipleObjects()返回,并重新开始,以包含这个新的 handle。