我做了如下的一个小实验程序:
在VC中创建一个MFC对话框工程Test,添加两个按钮IDC_BUTTON_CREATE_THREAD和IDC_BUTTON_EXIT_THREAD,分别用来控制创建子线程和结束子线程。它们的响应函数如下:
void CTestDlg::OnButtonCreateThread() 
{
// TODO: Add your control notification handler code here
DWORD dwThreadId = 0;
if (m_hThread == NULL)
{
m_hThread = ::CreateThread(0,0,ThreadProc,LPVOID(this),0,&dwThreadId);
}
}void CTestDlg::OnButtonExitThread() 
{
// TODO: Add your control notification handler code here
if (m_hThread != NULL)
{
// Set the flag that the thread must be stopped
m_bThreadStopping = TRUE;

// Wait until the watcher thread has stopped
::WaitForSingleObject(m_hThread,INFINITE);

// The thread has stopped
m_bThreadStopping = FALSE;

// Close handle to the thread
::CloseHandle(m_hThread);
m_hThread = 0;
}
}
上面的代码中m_hThread是保存子线程句柄的CTestDlg成员变量,m_bThreadStopping是CTestDlg的静态成员变量,用来指示子线程退出。子线程的代码如下:
DWORD WINAPI CTestDlg::ThreadProc( LPVOID lpParameter )
{
LARGE_INTEGER nStartCount, nCurCount; while (!m_bThreadStopping)
{
::QueryPerformanceCounter(&nStartCount);
do
{
::QueryPerformanceCounter(&nCurCount);
} while (nCurCount.QuadPart < nStartCount.QuadPart + 1000);
if (!m_bThreadStopping)
{
::SendMessage(((CTestDlg *)lpParameter)->GetSafeHwnd(), WM_APP+1, 0, 0);
}
}
return 999;
}
子线程是CTestDlg的静态成员函数,它的功能实际是每隔一定时间向主线程(UI线程)发送消息,相当于一个定时器。为达到准确的时间控制,消息发送用了SendMessage(),而不是PostMessage()。主线程中的消息响应如下:
LRESULT CTestDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
// TODO: Add your specialized code here and/or call the base class
static UINT uCount = 0;
if (message == WM_APP + 1)
{
SetDlgItemInt(IDC_STATIC, uCount, FALSE);
uCount++;
}
else
{
return CDialog::WindowProc(message, wParam, lParam);
}
}
就是在IDC_STATIC静态文本控件中显示接收到子线程消息的次数。问题就在于,有的时候当点击“结束线程”(也就是IDC_BUTTON_EXIT_THREAD按钮)时,程序再不响应用户操作,但静态文本控件中的计数值不变,疑似进入死锁状态。当子线程发送消息的间隔时间缩短时,这种现象更严重。我怀疑有可能是下面的情况发生了,就是主线程在等待子线程结束的时候被挂起(在执行WaitForSingleObject()时),但是子线程同时又在等待主线程返回消息处理结果(即子线程正在执行SendMessage())。但是我又觉得在子线程正在执行SendMessage()函数时,主线程应该不会响应用户点击“结束线程”的事件才是,因为SendMessage()发送的消息是Non-queued消息,而点击“结束线程”的消息是queued消息,它被放在消息队列中,至少应该等到子线程的SendMessage()调用主线程窗口的WindowProc()处理完子线程发送的消息之后才会被处理。这样说来,上面的死锁情形就不应该发生。想了一整天没弄明白为什么会发生这种现象,还望高手指点,在此先谢谢了!

解决方案 »

  1.   


      死锁了。主线程调用WaitForSingleObject(m_hThread,INFINITE);,子线程调用SendMessage(((CTestDlg *)lpParameter)- >GetSafeHwnd(), WM_APP+1, 0, 0); 这两个调用引起死锁。问题在于:SendMessage()发送的消息是Non-queued消息,这话不完全正确。对于同一个线程SendMessage()可以不经过消息队列,直接调用窗口函数,发送的是Non-queued消息;但是对于不同的线程,就不是这样的了。子线程不能直接调用主线程中的窗口过程的!跨线程用SendMessage()发送消息时,还是要经过消息队列的,只是优先级高一些而已。(原因好像是考虑到很多界面控件本身不是线程安全的,为了保护控件,详细说明可以看《windows核心编程》)   这样,主线程调用WaitForSingleObject(m_hThread,INFINITE);等待子线程结束时,不能处理子线程通过SendMessage(((CTestDlg *)lpParameter)- >GetSafeHwnd(), WM_APP+1, 0, 0);发送到消息队列中的消息,造成SendMessage()调用一直等待,引起死锁。建议使用MsgWaitForMultipleObjects()来等待子线程结束:DWORD dwRet;while(WAIT_OBJECT_0 != (dwRet = MsgWaitForMultipleObjects(1,&m_hThread,TRUE,INFINITE,QS_SENDMESSAGE)))
    // 处理子线程调用SendMesssage()发送的WM_APP+1消息
    if (WAIT_OBJECT_0 + 1 == dwRet)
    {
    MSG msg;
    while(PeekMessage(&msg,0,0,0,PM_REMOVE))
    DispatchMessage(&msg);
    }基本思路是这样的,代码没经过验证,详细情况请参考MSDN。
      
      

  2.   

    多谢yaozijian110的提醒,我回去试了下,但发现不用DispatchMessage(&msg); 这一句也不会发生死锁,也就是说只用PeekMessage就够了,不需要再对消息做更多的处理。这又是为什么呢?