主线程的两个特点

您可以任意转载这篇文章,但请在转载时注明原始链接和作者,谢谢。

本文链接:http://www.titilima.cn/show-544-1.html
先看一段引文,出处不甚清楚,但肯定是一本纸质出版物。

 

> 程序启动后就执行的那个线程称为主线程(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 文件,这里就不再多介绍了。

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

显而易见,ExitThread 的执行使得线程结束,也就是直接跳过了 CRT 之中的 ExitProcess,这样一来进程当然无法结束了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值