分享

主线程的两个特点

 quasiceo 2017-10-22


分享到饭否

先看一段引文,出处不甚清楚,但肯定是一本纸质出版物。

> 程序启动后就执行的那个线程称为主线程(primary thread),主线程有两
> 个特点,第一,它必须负责 GUI(Graphic User Interface)程序中的主消息循
> 环。第二,这一线程的结束(不论是因为返回或因为调用了 ExitThread())会
> 使得程序中的所有线程都被强迫结束,程序也因此而结束。其他线程没有机会
> 做清理工作。

我看书的时候很少字斟句酌,一般都是只了解个大概,而像这样的句子就基本属于我无视的内容。不过,在一个偶然的机会,我注意到了这句话,并有了以下这篇评论。

先说结论:句中所提到的“主线程的两个特点”,基本不靠谱。

> 第一,它必须负责 GUI(Graphic User Interface)程序中的主消息循环。
之所以说这句话不靠谱,是因为它过度地限制了主线程的功能。在我的印象中,灵图天行者 4.0 以前的版本(含)中,主线程是一个调度器,用于调度其余的工作线程,而 UI 线程则亦是这些工作线程中的一个。换句话说,主消息循环可以不在主线程之中。

> 第二,这一线程的结束(不论是因为返回或因为调用了 ExitThread())会使得程序中的所有线程都被强迫结束,程序也因此而结束。
这句话半对半错,因为如果在主线程中调用 ExitThread 的话,其它线程是不会退出的,因此程序也不会结束。考虑以下代码:

C++代码
  1. #include <Windows.h>   
  2.   
  3. DWORD WINAPI ThreadProc(PVOID param)   
  4. {   
  5.     for (;;)   
  6.     {   
  7.         Sleep(1000);   
  8.     }   
  9.     return 0;   
  10. }   
  11.   
  12. int main(void)   
  13. {   
  14.     HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);   
  15.     Sleep(1000);   
  16.     ExitThread(0);   
  17.     return 0;   
  18. }  

程序运行后,main 所在的线程被 ExitThread 结束,但 ThreadProc 的线程却会一直运行。对于这个问题,我在《Windows 编程札记》中的一段话能够很好的解释,如下。

---------- 传说中的分隔线 ----------

那么,就让我们来探索一下进程正常结束的过程吧。为了避免一切不必要代码的干扰,我选择了一个空无一物的骨架程序。

C++代码
  1. #include <Windows.h>   
  2.   
  3. int WINAPI WinMain(   
  4.     HINSTANCE hInstance,   
  5.     HINSTANCE hPrevInstance,   
  6.     LPSTR lpCmdLine,   
  7.     int nShowCmd)   
  8. {   
  9.     return 0;   
  10. }  

如你所见,这个程序实在是无愧于“骨架”这个称号——那真是除了骨头就是架子了。
从表面上来看,这个 WinMain 就是程序的入口。当它返回(return 0;)之后,我们的进程就结束了。当然,事实肯定没有看起来的这么简单,因为编译器是很乐于偷偷做好事的,而且做了好事还有拒不留名的习惯。
现在让我们编译这段代码,并使用 WinDbg 来调试这个程序。我们不设置任何断点,直接让程序运行到结束。在这个时侯,程序的调用堆栈信息会是下面这个样子:

WinDbg 输出
  1. 0:000> k   
  2. ChildEBP RetAddr   
  3. 0012fdc4 7c92e89a ntdll!KiFastSystemCallRet   
  4. 0012fdc8 7c81ca3e ntdll!ZwTerminateProcess+0xc   
  5. 0012fec4 7c81ca96 kernel32!_ExitProcess+0x62   
  6. 0012fed8 004012a1 kernel32!ExitProcess+0x14
  7. 0012fee4 0040148d skeleton!__crtExitProcess+0x17   
  8. 0012ff28 004014b7 skeleton!doexit+0x113   
  9. 0012ff3c 0040114f skeleton!exit+0x11   
  10. 0012ffc0 7c816ff7 skeleton!__tmainCRTStartup+0x121   
  11. 0012fff0 00000000 kernel32!BaseProcessStart+0x23  

真相终于大白于天下。很显然,编译器把 kernel32.dll 的 ExitProcess API 藏到了 WinMain 的后面,使得我们的 skeleton.exe 进程最终得以退出。如果你对如何封装 ExitProcess 的细节感兴趣的话,那么可以深入研究 Visual Studio 附带的 C runtime 源代码中的 crt0.c 文件,这里就不再多介绍了。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多