【问题标题】:Why a custom exception must have default parameter when raised in ProcessPoolExecutor?为什么在 ProcessPoolExecutor 中引发自定义异常时必须具有默认参数?
【发布时间】:2019-06-08 22:28:30
【问题描述】:

我有一个 ProcessPoolExecutor 并想在其中引发一个自定义异常。但它只有在具有默认参数或在超级初始化中使用或只是绕过该超级初始化函数时才可以在不破坏进程池的情况下工作。 我对这种行为一无所知。 在 Py3.7 和 3.8 上显示如下: A process in the process pool was terminated abruptly while the future was running or pending. 有什么想法吗?

from concurrent.futures.process import ProcessPoolExecutor


class PoolBreaker(Exception):
    def __init__(self, num):
        super().__init__()
        self.num = num


class NoPoolBreaker(Exception):
    def __init__(self, num=0):
        super().__init__()
        self.num = num


class NoPoolBreaker2(Exception):
    def __init__(self, num):
        super().__init__(num)
        self.num = num


class NoPoolBreaker3(Exception):
    def __init__(self, num):
        self.num = num


def get_result(job):
    exc = job.exception()
    if type(exc) is PoolBreaker:
        print("PoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker:
        print("NoPoolBreaker", exc.num)
    elif type(exc) is NoPoolBreaker2:
        print("NoPoolBreaker2", exc.num)
    elif type(exc) is NoPoolBreaker3:
        print("NoPoolBreaker3", exc.num)
    else:
        print(f"Exception: {str(exc)}")


def work(x: int):
    if x == 0:
        raise PoolBreaker(x)
    elif x == 1:
        raise NoPoolBreaker(x)
    elif x == 2:
        raise NoPoolBreaker2(x)
    elif x == 3:
        raise NoPoolBreaker3(x)


if __name__ == '__main__':
    for num in range(0, 4):
        with ProcessPoolExecutor() as executor:
            job = executor.submit(work, num)
        get_result(job)

【问题讨论】:

    标签: python multithreading exception


    【解决方案1】:

    我也遇到过这个问题,我很困惑。这只发生在 ProcessPoolExecutor 上,不会发生在 ThreadPoolExecutor 上。

    问题是 ProcessPoolExecutor 试图通过第二次创建来创建 Exception 的副本。为此,ProcessPoolExecutor 似乎检查了“上层”类——例如除了 NoPoolBreaker3 中的例外,其中上层类是 NoPoolBreaker3 本身——因为它的属性。由于 Exception 并不总是包含整个对象的所有属性,所以它会失败。

    如果你在构造函数中添加一个打印,你会看到:

    from concurrent.futures.process import ProcessPoolExecutor
    
    class PoolBreaker(Exception):
        def __init__(self, num):
            super().__init__()
            print(f"PoolBreaker Constructor with num={num}")
            self.num = num
    
    
    class NoPoolBreaker1(Exception):
        def __init__(self, num=-9):
            super().__init__()
            print(f"NoPoolBreaker1 Constructor with num={num}")
            self.num = num
    
    
    class NoPoolBreaker2(Exception):
        def __init__(self, num):
            super().__init__(num)
            print(f"NoPoolBreaker2 Constructor with num={num}")
            self.num = num
    
    
    class NoPoolBreaker3(Exception):
        def __init__(self, num):
            print(f"NoPoolBreaker3 Constructor with num={num}")
            self.num = num
    
    
    def get_result(job):
        exc = job.exception()
        if type(exc) is PoolBreaker:
            print("PoolBreaker", exc.num)
        elif type(exc) is NoPoolBreaker1:
            print("NoPoolBreaker1", exc.num)
        elif type(exc) is NoPoolBreaker2:
            print("NoPoolBreaker2", exc.num)
        elif type(exc) is NoPoolBreaker3:
            print("NoPoolBreaker3", exc.num)
        else:
            print(f"Exception: {str(exc)}")
    
    
    def work(x: int):
        if x == 0:
            raise PoolBreaker(x)
        elif x == 1:
            raise NoPoolBreaker1(x)
        elif x == 2:
            raise NoPoolBreaker2(x)
        elif x == 3:
            raise NoPoolBreaker3(x)
    
    
    if __name__ == '__main__':
        for num in range(0, 4):
            with ProcessPoolExecutor() as executor:
                job = executor.submit(work, num)
            get_result(job)
    
    
    

    及其输出(python 3.7.3):

    PoolBreaker Constructor with num=0
    Exception: A process in the process pool was terminated abruptly while the future was running or pending.
    NoPoolBreaker1 Constructor with num=1
    NoPoolBreaker1 Constructor with num=-9
    NoPoolBreaker1 1
    NoPoolBreaker2 Constructor with num=2
    NoPoolBreaker2 Constructor with num=2
    NoPoolBreaker2 2
    NoPoolBreaker3 Constructor with num=3
    NoPoolBreaker3 Constructor with num=3
    NoPoolBreaker3 3
    

    如您所见,异常的构造函数被调用了两次,但让我们来看看案例:

    • PoolBreaker:PoolBreaker 异常已成功创建。之后,lib 检查这个 Exception 对象并且没有找到任何属性。第二次调用构造函数,但没有传递参数,因此失败。它没有在 MWE 中完成,但如果您打印堆栈跟踪,您会看到它抱怨 TypeError: __init__() missing 1 required positional argument: 'num'
    • NoPoolBreaker1:它也没有找到任何属性,但是当使用零参数调用构造函数时,它不会失败,因为它有一个默认值(您在输出中看到 num=-9)
    • NoPoolBreaker2:由于使用参数调用 super(),因此 Exception 具有 num 属性。所以构造函数的第二次调用接收到所需的 num 属性和正确的值,所以它可以工作。
    • NoPoolBreaker3:没有调用 super() 并且检查了 NoPoolBreaker3,他找到了 num 属性。所以构造函数的第二次调用接收到所需的 num 属性和正确的值,所以它可以工作。

    实际返回的异常总是第一个,第二个好像被扔掉了。

    要在进程之间传输对象,必须对它们进行腌制和复制,这是由 ProcessPoolExecutor 完成的。这可能是第二次创建异常的原因。

    当然,这不是问题的解决方案,遗憾的是我还没有找到任何人。

    我同时认为,这可能是库中的一个错误,因为:

    • 不打算第二次创建对象,或者
    • 属性检查错误

    请注意:我目前遇到的问题是,在某个程序中 NoPoolBreaker3 不再工作,并且还会导致“突然终止”异常。我不知道为什么,但这可能意味着行为取决于比 MWE 中显示的更多的东西。

    【讨论】:

      【解决方案2】:

      这是/现在是 python pickle 中的一个错误,更多细节可以在这里找到:https://bugs.python.org/issue37208

      【讨论】:

        猜你喜欢
        • 2012-09-27
        • 2012-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-02
        • 1970-01-01
        • 2010-12-11
        相关资源
        最近更新 更多