打印Python中的守护线程异常

10 投票
2 回答
4181 浏览
提问于 2025-04-15 11:39

在Python中,如果在守护线程(daemon thread)中出现异常,它不会打印出错误追踪信息。

举个例子,这段代码创建了一个守护线程,并在新线程中抛出了一个异常:

def error_raiser():
    raise Exception

import threading
thread = threading.Thread(target=error_raiser)
thread.daemon = True
thread.start()

但是它不会打印出任何错误信息。(完全没有输出)。

不过,如果这个线程不是守护线程,Python就会打印出错误追踪信息。这里是同样的代码,只是注释掉了一行:

def error_raiser():
    raise Exception

import threading
thread = threading.Thread(target=error_raiser)
# thread.daemon = True
thread.start()

输出结果是:

Exception in Thread-1:
Traceback (most recent call last):
  File "C:\Python26\lib\threading.py", line 525, in __bootstrap_inner
    self.run()
  File "C:\Python26\lib\threading.py", line 477, in run
    self.__target(*self.__args, **self.__kwargs)
  File "test.py", line 2, in error_raiser
    raise Exception
Exception

在Python 2.6.2和Python 3.0.1中运行这段代码,结果都是一样的。有趣的是,如果我在IPython环境中导入并执行这段代码,无论线程是否是守护线程,异常信息都会显示出来。

根据文档,'daemon'标志的唯一意义是“当只剩下守护线程时,整个Python程序会退出。”这让我觉得在异常发生后不打印错误追踪信息可能是Python的一个bug,除非我在文档中漏掉了什么。

这真的是一个bug吗?还是我在文档中遗漏了什么,这种行为是故意的?如果这是故意的,我该如何在不使用IPython的情况下强制Python在守护线程中打印错误追踪信息呢?

2 个回答

3

这是个bug吗,还是我在文档中漏掉了什么,这种行为是故意的?

其实你自己已经说出了原因,只是没有意识到:

根据文档,'daemon'标志唯一的重要性在于“当只剩下守护线程时,整个Python程序会退出。”

如果你有一个非守护线程,Python会在你调用thread.start()后等待这个线程完成。这种等待包括线程所做的任何事情,比如抛出和处理异常。

而如果你有一个守护线程,Python在调用thread.start()后就不会等待它。相反,如果没有其他指令,Python会立即退出。这意味着你的线程根本没有机会去抛出或处理异常。


如果这是故意的,我该如何强制Python在守护线程中打印异常追踪信息,而不使用IPython?

对于一个daemon线程来说,它应该做什么其实并不重要。如果它被指示去打印一些东西,结果也是一样的。

这也意味着你无法有条件地等待守护线程的某个动作。要么你把thread.daemon = False,这样你就能得到所有的异常追踪信息,以及所有的打印、输入输出和其他操作。要么你把thread.daemon = True,这样在其他线程都结束后,你就得不到任何异常追踪信息,也得不到打印、输入输出或其他操作。


有趣的是,如果我在IPython环境中导入代码执行,无论线程是否是守护线程,异常都会显示出来。

关于shell的一个特点是,它们不会退出,除非你强制结束它们。由于shell的解释器在等待你的输入时不会退出,所以任何启动的daemon线程都会保持活着。

9

根据维基百科的定义,守护进程(daemon)应该和控制终端(tty)分离,所以我觉得没有异常信息显示是正确的(毕竟,守护进程即使在你关闭启动它的命令行后也应该继续工作)。
你可以在这里查看相关信息。

至于如何打印错误追踪信息,我觉得简单的用try/except然后记录到文件就可以解决这个问题了 :)

撰写回答