马大叔的工作日记

windows是一个消息驱动的系统,也是个多任务调度系统,windows中的线程分为两类,GUI线程与Worker线程,每个GUI线程会关联消息队列,当消息处理顺序不当时,则有可能造成消息死锁。

使用VS2008打开项目工程,按F5启动调试,该工具工作正常,点击退出按钮,此时会发现该工具失去了响应。按Ctrl+Alt+Break将程序中断,发现程序停在了如下位置。

可以看出,当接收到退出消息时,该函数会被调用,函数内部设置了退出事件,然后等待另一个线程退出,不过看来WaitForSingleObject并没有返回,也就是说另一个线程并没有退出,通过阅读代码得知其执行函数为CXXXXToolDLg::g_ThreadXXXX,调出线程窗口列表,并转到该线程。

查看该线程的调用栈,发现其当前停在了User32.dll!_NtUserMessageCall函数,向下追溯发现,是该线程调用了CListCtrl::GetItemCount,而在GetItemCount中又调用了SendMessage()的原因,看来问题就出现在这个SendMessage了,不过该函数为何不能返回?

查阅MSDN,对该函数的解释如下:

如果SendMessage发送消息的目标窗口是该调用线程自己产生的,那么消息处理函数会类似子程序一样立即得到调用。如果目标窗口是另一线程产生的,系统会切换到接收该消息的线程,然后调用对应的消息处理函数。在线程之间传递的消息只有当接收线程执行获取消息的代码的时候才被处理。发送消息的线程会一直阻塞,直到接收消息的线程处理完成。

这段话不是很好理解,那么就从消息机制的实现来进行分析。

对于每个windows线程,当线程刚被创建时,所有线程都不是GUI线程,只有当线程使用Windows子系统内核服务(win32k.sys)时,Windows才将线程转换为GUI线程。同时,每个GUI线程将关联一个THREADINFO结构,这个结构中包含四个消息队列。

1. Send Message Queue发送消息队列

2. Posted Message Queue登记消息队列

3. Visualized Input Queue输入消息队列

4. Reply Message Queue响应消息队列

Sent Message Queue:该队列保存其他程序通过SendMessage给该线程发送的消息

Posted Message Queue:该队列保存其他队列通过PostMessage给该线程发送的消息

Visualized Input Queue:保存系统队列分发过来的消息,比如鼠标或者键盘的消息

Reply Message Queue:保存向窗体发送消息后的结果,比如sendMessage操作结束后,接收消息方会发送一个Reply消息给发送方的Reply队列中,以唤醒发送队列。

每个GUI程序,都有一个消息循环,,不断的通过GetMessage从消息队列中取出消息,并用DispatchMessage发送给该消息的消息响应函数,实际上是DispatchMessage调用了该消息的响应函数。

对于SendMessage函数,则有如下两种情形:

1. 当发送消息的目标窗口是调用线程自己创建时:

SendMessage会直接调用该消息的响应函数,即不需要通过消息循环。

2. 当发送消息的目标窗口是另一线程创建时:

SendMessage首先将该消息挂入接收线程的发送消息队列(Send Message Queue),然后将自身阻塞,系统调度至接收线程,并且当接收线程调用操作消息队列的函数时(经过测试:发现GetMessage或PeekMessage都可以),会取出该消息,并直接调用该消息的响应函数(注意,此时并不需要DispatchMessage),当该消息处理完成后,接收线程会发送一个Reply消息给发送方的Reply队列中,以唤醒发送队列。

因此在这个程序中,当点击退出按钮时时,主线程设置退出事件,将自身阻塞,开始等待子线程退出,而子线程则使用了SendMessage向主窗口发送了消息,并只有该消息完成才会返回,此时系统会切换到主线程,但主线程也在等待子线程,没有办法去调用操作消息队列的函数,因此形成了互相等待的局面,即死锁。

改进的方法有很多,但为了不破坏程序原有结构,采用的方法是让主线程既能等待子线程退出,同时也要能处理消息。微软为了解决这一问题,提供了一个API函数,声明如下:

DWORD WINAPI MsgWaitForMultipleObjects(

_In_DWORD nCount,

_In_const HANDLE *pHandles,

_In_BOOL bWaitAll,

_In_DWORD dwMilliseconds,

_In_DWORD dwWakeMask

);

对的,坚持;错的,放弃!

马大叔的工作日记

相关文章:

你感兴趣的文章:

标签云: