Chinaunix首页 | 论坛 | 博客
  • 博客访问: 368136
  • 博文数量: 715
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5005
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:46
文章分类

全部博文(715)

文章存档

2011年(1)

2008年(714)

我的朋友

分类:

2008-10-13 16:30:57

除虫记之八(WM_QUIT和结束线程:1小时/1人)

公测的时候又发现一个问题,因为一个商务上问题,程序中去掉了一个模块(dll)的调用,打包公测后,发现程序在退出后而聊天进程没有退出,等待了很长时间也没有退出。

开发人员在调试的时候,已经在debug版本下重现了这个bug,跟踪代码出错部分的流程如下:
发送WM_QUIT消息给一个工作线程让其退出;
用WaitForSingleObject等待那个线程的句柄有信号;
如果有信号,就直接CloseHandle掉这个线程句柄;
如果超时没有信号,就直接用_endthreadex()杀死线程。

开发人员跟踪了好多次发现消息发送过去后线程并没有退出,每次都要超时进入强杀流程,但强杀调用确没有杀死线程,从thread里面还可以明确的看到这个线程在运行,于是就导致了进程没有退出!

跟踪了好几次都找不到问题所在,让我过去看看。

很明显,进程没有退出,就是因为这个线程没有退出的缘故。我一开始怀疑是不是线程没有收到这个消息,于是在处理消息的地方加上断点,ft,结果频频进入断点,程序一下子就失去了反应,连VC都没有响应了,不得已在进程管理器中杀死,按说在调试状态,进程管理器是不让杀的,但不知为何偏偏杀死了,且不管他。把断点去掉,在Post线程消息前加断点,然后再在线程的消息处理中加断点。运行,Post线程消息成功,F5运行,靠,竟然没有走到线程消息处理的断点里面。线程没有收到消息????

运行多次,都是同一个现象。

因为只是一个杀死线程的操作,想让线程死还不容易啊,于是建议开发人员用TerminateThread()强杀线程,开发人员开始不理解,说线程是用_beginthreadex启动的,应该用_endthreadex杀死。我说就按我说的做,呵呵,线程如期被我们干掉了,进程退出了。

趁此机会,又给开发人员讲解了一番C运行库函数和Win32API的优劣比较。

让他们编译release版后送给测试部去验证,我又开始仔细那个线程处理消息的流程,我打了好多调试语句,怎么都发现没有收到退出的消息。晕啊。

忽然,注意到了WM_QUIT消息,哈,想起来了,自己曾经又一次就是用这个消息让线程退出,但没有成功,后来改程自定义的消息就可以了,立刻动手改成WM_USER+113,哈,收到了退出的消息,线程如期自动退出了!

哈哈,立刻又第n次的简单看了看WM_QUIT消息,然后告诉开发人员说WM_QUIT消息只能由应用程序主线程处理做程序退出。

我犯了一个大错误!

刚转了一圈回来,开发人员小声的说不是WM_QUIT消息的问题,被我听到了。
立刻追问,原来开发人员第一次的认真看了看WM_QUIT消息的MSDN文档,发现并不是线程不能处理WM_QUIT消息,而是线程中用GetMessage接收消息,而接收到这个消息后GetMessage返回0,就是这个0,被程序用if给if掉了!

我犯了一个大错误!我以为我对API已经很熟悉了,但看来远远不是!远远不是!

教训:尽可能用Win32的API,尽量的少用 c 运行库函数。WM_QUIT消息可以被任何线程处理。


--------------------next---------------------

 re: 除虫记之八:WM_QUIT和结束线程

CreateThread()和_beginthreadex()在Jeffrey的《Windows核心编程》中讲的很清楚,应当尽量避免使用CreateThread()。 
事实上,_beginthreadex()在内部先为线程创建一个线程特有的tiddata结构,然后调用CreateThread()。在某些非线程安全的CRT函数中会请求这个结构。如果直接使用CreateThread()的话,那些函数发现请求的tiddata为NULL,就会在现场为该线程创建该结构,此后调用EndThread()时会引起内存泄漏。_endthreadex()可以释放由CreateThread()创建的线程,实际上,在它的内部会先释放由_beginthreadex()创建的tiddata结构,然后调用EndThread()。 
因此,应当使用_beginthreadex()和_endthreadex(),而避免使用CreateThread()和EndThread()。


在MFC为什么要用AfxBeginThread,因为AfxBeginThread可以更好跟MFC配合,不然微软也至于写这样的API了


你说:MFC的bug多了去了。你以为Afx函数就不会出问题? 
请举例说明,否则我认为你对API一知半解造成片面认识


2006-01-06 14:04 |

--------------------next---------------------

 re: 除虫记之八:WM_QUIT和结束线程

为了把问题说清楚,楼主产品的代码可能大概是这个样子的

int work_thread(void *param)
{
MSG msg;
bool isExit = false;
while(!isExit)
{
if(GetMessage(&msg,0,0,0))  
{
switch(msg)
{
case WM_QUIT:
isExit = true;
break;
//...
}
}
}
}


int main_thread_some_function()
{
DWORD nThreadID;
HANDLE hThread = (HANDLE)_beginthreadex( NULL, 0, &work_thread, NULL, 0, &threadID );
...

//send WM_QUIT message
PostThreadMessage(nThreadID,WM_QUIT,0,0);

int timeout = 3000;
int nResult = WaitForSingleObject(hThread,timeout);

if(nResult == WAIT_TIMEOUT) //timeout
{
_endthreadex();
}
else if(nResult == WAIT_OBJECT_0) //ok
{
CloseHandle(hThread);
}
}


这个程序的bug有两处:
一个就是if(GetMessage(&msg,0,0,0))  处理不当
这样写
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)

    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
       //process msg here
    }
}
另外一个就是错误的使用了_endthreadex();这个是用来结束自己的thread,而不是来杀掉work_thread的。

所以应该改成这样

int work_thread(void *param)
{
MSG msg;
bool isExit = false;
while(!isExit)
{
int bRet = GetMessage(&msg,0,0,0);
if(bRet>0)
{
switch(msg)
{
}
}
else if(bRet == 0)
{
break;
}
else //failed
{
//process failed
}
}
}


int main_thread_some_function()
{
DWORD nThreadID;
HANDLE hThread = (HANDLE)_beginthreadex( NULL, 0, &work_thread, NULL, 0, &threadID );

...

//send WM_QUIT message
PostThreadMessage(nThreadID,WM_QUIT,0,0);

int timeout = 3000;
int nResult = WaitForSingleObject(hThread,timeout);

if(nResult == WAIT_TIMEOUT) //timeout
{
TerminateThread(nThread);
}
else if(nResult == WAIT_OBJECT_0) //ok
{
CloseHandle(hThread);
}
else
{
   //error process
}
}

2006-01-06 16:27 |

--------------------next---------------------

 re: 除虫记之八:WM_QUIT和结束线程

原来,线程函数返回时,系统并不立即将它撤消。相反,系统要取出这个即将被撤消的线程,让它调用已经映射的DLL的所有带有DLL_THREAD_DETACH值的、且没有调用DisableThreadLibraryCalls函数的DllMain函数。DLL_THREAD_DETACH通知告诉所有的DLL执行每个线程的清除操作,例如,DLL版本的C/C++运行期库能够释放它用于管理多线程应用程序的数据块。DisableThreadLibraryCalls函数告诉系统说,特定的DLL的DllMain函数不用接收DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知。
但是,系统是顺序调用DLL的DllMain函数的。当进程被创建时,系统也为该进程创建了一个互斥对象。每个进程都有它自己的互斥对象。当线程调用映射到进程的地址空间中的DLL的DllMain函数时,这个互斥对象负责对进程的所有线程实施同步。
当线程函数返回时,系统检查进程中是否存在没有调用DisableThreadLibraryCalls函数的DllMain函数,如果存在,系统就以进程的互斥对象的句柄作为第一个参数,在线程内部调用WaitForSingleObject函数。一旦这个将要终止运行的线程拥有该进程互斥对象,系统就让该线程用DLL_THREAD_DETACH的值依次调用每个没有调用DisableThreadLibraryCalls函数的DLL的DllMain函数。此后,系统才释放对进程互斥对象的所有权。
2006-03-14 11:10 |

--------------------next---------------------

 re: 除虫记之八:WM_QUIT和结束线程

原来,线程函数返回时,系统并不立即将它撤消。相反,系统要取出这个即将被撤消的线程,让它调用已经映射的DLL的所有带有DLL_THREAD_DETACH值的、且没有调用DisableThreadLibraryCalls函数的DllMain函数。DLL_THREAD_DETACH通知告诉所有的DLL执行每个线程的清除操作,例如,DLL版本的C/C++运行期库能够释放它用于管理多线程应用程序的数据块。DisableThreadLibraryCalls函数告诉系统说,特定的DLL的DllMain函数不用接收DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知。 
但是,系统是顺序调用DLL的DllMain函数的。当进程被创建时,系统也为该进程创建了一个互斥对象。每个进程都有它自己的互斥对象。当线程调用映射到进程的地址空间中的DLL的DllMain函数时,这个互斥对象负责对进程的所有线程实施同步。 
当线程函数返回时,系统检查进程中是否存在没有调用DisableThreadLibraryCalls函数的DllMain函数,如果存在,系统就以进程的互斥对象的句柄作为第一个参数,在线程内部调用WaitForSingleObject函数。一旦这个将要终止运行的线程拥有该进程互斥对象,系统就让该线程用DLL_THREAD_DETACH的值依次调用每个没有调用DisableThreadLibraryCalls函数的DLL的DllMain函数。此后,系统才释放对进程互斥对象的所有权。 
结果就导致了死锁。
2006-03-14 11:12 |

--------------------next---------------------

 re: 除虫记之八:WM_QUIT和结束线程

原来,线程函数返回时,系统并不立即将它撤消。相反,系统要取出这个即将被撤消的线程,让它调用已经映射的DLL的所有带有DLL_THREAD_DETACH值的、且没有调用DisableThreadLibraryCalls函数的DllMain函数。DLL_THREAD_DETACH通知告诉所有的DLL执行每个线程的清除操作,例如,DLL版本的C/C++运行期库能够释放它用于管理多线程应用程序的数据块。DisableThreadLibraryCalls函数告诉系统说,特定的DLL的DllMain函数不用接收DLL_THREAD_ATTACH和DLL_THREAD_DETACH通知。  
但是,系统是顺序调用DLL的DllMain函数的。当进程被创建时,系统也为该进程创建了一个互斥对象。每个进程都有它自己的互斥对象。当线程调用映射到进程的地址空间中的DLL的DllMain函数时,这个互斥对象负责对进程的所有线程实施同步。  
当线程函数返回时,系统检查进程中是否存在没有调用DisableThreadLibraryCalls函数的DllMain函数,如果存在,系统就以进程的互斥对象的句柄作为第一个参数,在线程内部调用WaitForSingleObject函数。一旦这个将要终止运行的线程拥有该进程互斥对象,系统就让该线程用DLL_THREAD_DETACH的值依次调用每个没有调用DisableThreadLibraryCalls函数的DLL的DllMain函数。此后,系统才释放对进程互斥对象的所有权。  
结果就导致了死锁。 
2006-03-14 11:12 |

--------------------next---------------------

阅读(325) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~