You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python多线程中捕获KeyboardInterrupt后仍出现回溯的原因

问题:为何捕获KeyboardInterrupt后仍出现线程模块的回溯信息?

我正在尝试理解Python中异常抛出的工作机制,编写了如下代码:

import threading
import time


def first_thread():
    print(f'Start first thread')
    time.sleep(2)
    print(f'End first thread')


def second_thread():
    print(f'Start second thread')
    time.sleep(5)
    print(f'End second thread')


if __name__ == '__main__':
    first = threading.Thread(target=first_thread)
    second = threading.Thread(target=second_thread)
    try:
        print(f'Current number of threads: {threading.active_count()}')
        first.start()
        second.start()
        print(f'Current number of threads: {threading.active_count()}')
        first.join()
        second.join()
        print(f'Current number of threads: {threading.active_count()}')
    except KeyboardInterrupt as exc:
        print(f'Get the following exception: {exc}')

运行该代码后的输出为:

test_test % python3 test_simple.py
Current number of threads: 1
Start first thread
Start second thread
Current number of threads: 3
^CGet the following exception: 
^CException ignored in: <module 'threading' from '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py'>
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/threading.py", line 1307, in _shutdown
    lock.acquire()
KeyboardInterrupt

我的问题是:为何触发KeyboardInterrupt异常时,try-except语句已捕获该异常,但控制台仍会出现回溯信息?


回答

出现这个问题的核心原因是:你的try-except只捕获了主线程中join()调用时触发的KeyboardInterrupt,但Python解释器退出前的线程清理阶段,又在内部的_shutdown函数里触发了一次未被捕获的KeyboardInterrupt。

具体过程如下:

  • 按下Ctrl+C时,信号会发送给整个进程,所有线程都会收到KeyboardInterrupt。主线程在执行first.join()或second.join()时触发异常,被你的try-except捕获,打印提示后主线程继续执行到脚本末尾,进入退出流程。
  • 解释器退出时,会调用threading模块的_shutdown函数,该函数会尝试获取线程内部的锁来清理残留的线程资源。此时后台线程可能还处于sleep状态,再次触发KeyboardInterrupt,导致lock.acquire()调用抛出异常。
  • 这个异常发生在解释器的清理阶段,不在你的try-except代码块覆盖范围内,因此会被解释器当作未捕获异常打印回溯信息。

要解决这个问题,有两种常见方式:

  • 设置子线程为守护线程:创建线程时添加daemon=True参数,这样主线程退出时子线程会被强制终止,避免解释器进入复杂的清理流程:
    first = threading.Thread(target=first_thread, daemon=True)
    second = threading.Thread(target=second_thread, daemon=True)
    
  • 主动控制子线程退出:可以通过设置全局标志位,让子线程在循环中检查标志位主动退出,结合join()等待子线程结束,避免残留线程触发清理异常。

内容的提问来源于stack exchange,提问作者Daniil Stepanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 10:13:13