sys.excepthook 在 multiprocessing.Process 被忽略了?

sys.excepthook in multiprocessing.Process ignored?

假设我们有两个文件,即 mymanger.pymysub.py

mymanager.py

import time
from multiprocessing import Process

import mysub  # the process file


def main():

    xprocess = Process(
        target=mysub.main,
        )

    xprocess.start()
    xprocess.join()

    time.sleep(1)
    print(f"== Done, errorcode is {xprocess.exitcode} ==")


if __name__ == '__main__':
    main()

mysub.py

import sys


def myexception(exc_type, exc_value, exc_traceback):
    print("I want this to be printed!")
    print("Uncaught exception", exc_type, exc_value, exc_traceback)


def main():
    sys.excepthook = myexception  # !!!
    raise ValueError()


if __name__ == "__main__":
    sys.exit()

执行mymanager.py时,结果输出为:

Process Process-1:
Traceback (most recent call last):
  File "c:\program files\python.9\lib\multiprocessing\process.py", line 315, in _bootstrap
    self.run()
  File "c:\program files\python.9\lib\multiprocessing\process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\lx\mysub.py", line 11, in main
    raise ValueError()
ValueError
== Done, errorcode is 1 ==

当我预期的输出类似于:

I want this to be printed!
Uncaught exception <class 'ValueError'>  <traceback object at 0x0000027B6F952780>

如果我在没有 multiprocessing.Process.

的情况下从 mysub.py 执行 main,我会得到什么

我检查了底层的 cpython (reference),问题似乎是 _boostrap 函数中的 try-except 优先于我的子进程 sys.excepthook 但根据我的理解,不应该先触发子进程的例外钩子,然后触发 _boostrapexcept

我需要子进程使用sys.excepthook函数来处理异常。 我怎样才能做到这一点?

sys.excepthook 在异常未被捕获时调用(从 运行ning 程序中一直冒出)。但是 Process objects 运行 它们的目标函数在一个特殊的 bootstrap 函数中(BaseProcess._bootstrap 如果它对你很重要)它有意捕获所有异常,打印有关失败进程的信息以及回溯,然后 returns 调用者的退出代码(启动器因启动方法而异)。

当使用 fork start 方法时,_bootstrap 的调用者然后使用 os._exit(code) 退出 worker(一个绕过正常异常处理系统的“硬退出”命令,尽管因为您的异常已经被捕获并处理,所以这无关紧要)。当使用 'spawn' 时,它使用 sys.exit 而不是 os._exit,但是据我所知 sys.exit 实现的 SystemExit 异常在解释器中是特殊情况所以它在未被捕获时不会通过 sys.excepthook (大概是因为它通过异常实现被认为是一个实现细节;当你 要求 退出程序时它与死亡不同出现意外异常)。

总结:无论启动方法如何,您的代码引发的异常都不可能“未处理”(达到 sys.excepthook 的目的),因为 multiprocessing 处理 所有 函数可以自行抛出的异常 理论上 可能 excepthook 您在工作程序中设置的异常引发 之后 target 完成,如果multiprocessing 包装器代码本身会引发异常,但前提是你做了病态的事情,比如替换 os._exitsys.exit 的定义(它只会报告因为你替换了它们而发生的可怕事情,你自己的异常已经被那个点吞没了,所以不要那样做)。


如果您真的想这样做,最接近的方法是显式捕获异常并手动调用您的处理程序。例如,一个简单的包装函数将允许这样做:

def handle_exceptions_with(excepthook, target, /, *args, **kwargs)
    try:
        target(*args, **kwargs)
    except:
        excepthook(*sys.exc_info())
        raise  # Or maybe convert to sys.exit(1) if you don't want multiprocessing to print it again

将您的 Process 启动更改为:

    xprocess = Process(
        target=handle_exceptions_with,
        args=(mysub.myexception, mysub.main)
    )

或者为了one-off使用,偷懒只把mysub.main重写为:

def main():
    try:
        raise ValueError()
    except:
        myexception(*sys.exc_info())
        raise  # Or maybe convert to sys.exit(1) if you don't want multiprocessing to print it again

并保持其他一切不变。您仍然可以在 sys.excepthook and/or threading.excepthook() 中设置您的处理程序(以处理在工作进程中启动的线程可能因未处理的异常而终止的情况),但它不适用于工作进程的主线程(或者更准确地说,异常无法到达它)。