主线程的两个特点

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

本文链接: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,这样一来进程当然无法结束了。

### 回答1: 在 Python 中有许多方法可以执行并发(concurrent)操作。下面是一个使用多线程threading)模块的示例: ```python import threading def function_1(): # 执行函数 1 的代码 print("Function 1") def function_2(): # 执行函数 2 的代码 print("Function 2") # 创建两个线程 thread_1 = threading.Thread(target=function_1) thread_2 = threading.Thread(target=function_2) # 启动两个线程 thread_1.start() thread_2.start() # 等待两个线程结束 thread_1.join() thread_2.join() # 所有线程执行完毕后,程序继续执行 print("Done!") ``` 在这个例子中,我们使用了 threading 模块中的 Thread 类来创建两个线程,并设置它们的 target 属性为要执行的函数。然后,我们使用 start() 方法启动这两个线程。最后,我们使用 join() 方法等待这两个线程结束。 如果你希望使用其他方法来执行并发操作,例如进程(processes)或协程(coroutines),可以查看 Python 的 multiprocessing 和 asyncio 模块。 ### 回答2: Python提供了多种方法来实现并发执行两个函数的示例。下面以使用`threading`模块和`multiprocessing`模块为例进行说明。 1. 使用`threading`模块实现并发执行两个函数的示例: ```python import threading def func1(): # 第一个函数的逻辑 pass def func2(): # 第二个函数的逻辑 pass if __name__ == '__main__': # 创建两个线程并指定目标函数 t1 = threading.Thread(target=func1) t2 = threading.Thread(target=func2) # 启动线程 t1.start() t2.start() # 等待线程执行完毕 t1.join() t2.join() ``` 在这个示例中,通过创建两个`Thread`对象,并分别指定目标函数为`func1`和`func2`,然后使用`start`方法启动这两个线程。使用`join`方法等待两个线程执行完毕。 2. 使用`multiprocessing`模块实现并发执行两个函数的示例: ```python import multiprocessing def func1(): # 第一个函数的逻辑 pass def func2(): # 第二个函数的逻辑 pass if __name__ == '__main__': # 创建两个进程对象并指定目标函数 p1 = multiprocessing.Process(target=func1) p2 = multiprocessing.Process(target=func2) # 启动进程 p1.start() p2.start() # 等待进程执行完毕 p1.join() p2.join() ``` 在这个示例中,通过创建两个`Process`对象,并分别指定目标函数为`func1`和`func2`,然后使用`start`方法启动这两个进程。使用`join`方法等待两个进程执行完毕。 这两种方法都可以并发执行两个函数,但是线程是在同一个进程中执行的,而进程则是在不同的进程中执行的。选择使用哪种方法取决于你的具体需求和情况。 ### 回答3: Python的并发执行可以使用多线程或者协程来实现。下面我将分别以多线程和协程的方式给出一个示例。 1. 多线程示例: ```python import threading import time def func1(): print("开始执行函数1") time.sleep(3) print("函数1执行完毕") def func2(): print("开始执行函数2") time.sleep(2) print("函数2执行完毕") if __name__ == "__main__": t1 = threading.Thread(target=func1) t2 = threading.Thread(target=func2) t1.start() t2.start() t1.join() t2.join() print("线程结束") ``` 在上面的示例中,我们定义了两个函数`func1`和`func2`,分别用于执行一些任务。通过创建两个线程`t1`和`t2`,并将对应的函数作为参数传递给`Thread`类,我们可以实现这两个函数的并发执行。在`main`函数中,我们调用了`start`方法来启动这两个线程,然后通过`join`方法等待线程执行完毕,最后线程结束。 2. 协程示例: ```python import asyncio async def func1(): print("开始执行函数1") await asyncio.sleep(3) print("函数1执行完毕") async def func2(): print("开始执行函数2") await asyncio.sleep(2) print("函数2执行完毕") if __name__ == "__main__": loop = asyncio.get_event_loop() tasks = [func1(), func2()] loop.run_until_complete(asyncio.wait(tasks)) print("协程结束") ``` 在上面的示例中,我们定义了两个协程`func1`和`func2`,同样用于执行一些任务。通过创建一个事件循环`loop`,然后将这两个协程包装成任务对象`tasks`,我们可以使用`run_until_complete`方法来运行这些任务。最后协程执行完毕,事件循环结束。 以上就是使用多线程和协程实现Python并发执行两个函数的示例。两种方式各有特点,可以根据具体的需求选择合适的方式来实现并发执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值