如何优雅杀死python线程?
工作中写了一个脚本工具,7个系统的某一动作会触发脚本的相关操作,而系统相互独立没有先后顺序,因此我起了7个线程分别监听系统的动作。在测试过程中,如果发现脚本没有按预期进行,我会输入Ctrl+C杀死线程。测试过程中,我注意到每次都需要输入2次Ctrl+C才能正常杀死线程。
然而输入2次并不是什么难事,我也没有深究。昨天重试脚本时发现,在杀死线程重启程序后,程序明显变慢,等待数分钟都没有输出日志。推测是因为之前被杀死的线程没有被正确清理和关闭,当我强制终止程序时,一些线程可能处于“僵尸状态”,这些僵尸线程会占用系统资源,导致新启动的程序要等待资源释放以及线程被正确清理之后才能执行。
话不多说,当前的线程代码如下:
1 | for system in systems_list |
Q1:为什么要输入两次Ctrl+C才能杀死线程?
主线程和子线程
在上面的代码中,主线程是执行thread.start()
的那部分代码的线程,而report
函数所在的线程是子线程。
主线程:默认情况下,当你运行一个Python程序时,程序的主线程就开始执行。在上面的例子中,主线程就是创建和启动子线程的那部分代码所在的线程。
子线程:由主线程创建并启动的线程,用于并行执行任务。在上面的例子中,
report
函数所在的线程就是子线程。
杀死线程发生了什么
- 当按下第一次Ctrl+C时,系统会发送停止信号给主线程,但由于主线程下还有多个子线程,因此主线程还在等待子线程完成才会结束。
- 而子线程此时不知道发生了什么,仍然正在运行。
- 第二次按下Ctrl+C时,会强制终止子线程,所有线程被强制杀死。
守护线程
守护线程是一种特殊的线程,它在后台运行,并且在主程序退出时会自动结束。因此将子线程都设置成守护线程时,主程序被杀死时子线程也会一并结束。
1 | tread.setDaemon(True) |
结果
将子线程都设置成守护线程,按下一次Ctrl+C即可退出程序,杀死所有线程。
Q2:要怎么正确杀死线程,不影响下一次执行?
在主线程中捕获 KeyboardInterrupt
,并设置退出标志,通知子线程优雅地退出。
1 | import threading |
使用退出标志时,务必注意线程的同步问题。如果多个线程同时访问和修改退出标志,可能会导致竞争条件。为避免这种情况,可以加锁threading.Lock
或threading.Event
来同步对标志的访问。下面展示使用Event的代码。
1 | import threading |
这样,使用 try/except 块来确保资源被正确释放,实现优雅的关闭机制,让线程有机会清理资源的改造就完成了。
附
Event是Python中用于线程同步的一个工具,主要用于协调多个线程的操作,避免死锁。
Event对象由threading模块提供,主要有两种状态:已设置和未设置。当Event被设置时,所有等待该Event的线程都会被唤醒;当Event未被设置时,所有等待该Event的线程都会被阻塞,直到Event被设置。Event的主要方法包括:
- set():将Event设置为已设置状态,并通知所有等待该Event的线程。
- clear():将Event重置为未设置状态。
使用场景:
- 减少锁的使用:可以减少对锁的依赖,避免死锁问题。
- 线程间通信:Event可以让多个线程通过信号来同步操作。