为什么mp.Pool快速失败,但ProcessPoolExecutor没有失败?

时间:2019-11-25 12:03:54

标签: python exception multiprocessing

1。

    with ProcessPoolExecutor() as executor:
        futures = [executor.submit(foo, user_number, user_id)
                   for user_number, user_id in enumerate(user_ids, start=1)]

        for future in as_completed(futures):
            future.result()

2。

    pool = Pool()
    results = [pool.apply_async(foo, args=(user_number, user_id))
               for user_number, user_id in enumerate(user_ids, start=1)]
    for result in results:
        result.get()
    pool.close()
    pool.join()

3。

    pool = Pool()
    results = [pool.apply_async(foo, args=(user_number, user_id))
               for user_number, user_id in enumerate(user_ids, start=1)]
    try:
        for result in results:
            result.get()
    finally:
        pool.close()
        pool.join()

foo做一些工作,然后引发Value错误。

在使用第一个示例时,我只有在所有期货都完成后才能获得异常。
在第二个示例中,当第一个作业失败时,我得到了异常。
在第三个示例中,它的行为类似于第一个

我如何快速失败,仍然在退出之前清理资源?

为什么会这样?根据文档,as_completed会在期货交易完成后立即返回期货,调用future.result()应该会引发异常。

Python版本是3.6.9

1 个答案:

答案 0 :(得分:1)

问题是Python无法安全取消已经开始的作业。区别仅在于您告诉Python要做的事情:

  • 情况1:future.result()引发了异常 。然后,控制流脱离with语句,并且触发ProcessPoolExecutor.__exit__。默认情况下,这会等待所有待处理的作业完成,因此执行会挂起,直到出现这种情况为止。

  • 情况2:遇到异常时,Python解释器立即退出。但这并不意味着您的作业已停止运行!您只是从不等待它们完成。

  • 情况3:引发异常后,您调用pool.join(),它与情况1的情况大致相同。执行等待作业完成,然后退出。 / p>

您可以使用此脚本检查情况2的确切情况:

import signal
from multiprocessing import Pool
import time


def throw():
    raise ValueError()


def foo():
    def sigterm_handler(*args):
        print('received sigterm')
        raise SystemExit()

    signal.signal(signal.SIGTERM, sigterm_handler)

    while True:
        print('still alive')
        time.sleep(0.1)

pool = Pool()
results = [pool.apply_async(throw), pool.apply_async(foo)]
time.sleep(1)

for result in results:
    result.get()

pool.close()
pool.join()

在OSX上,输出:

$ python mp_test.py
still alive
still alive
still alive
still alive
still alive
still alive
still alive
still alive
still alive
still alive
multiprocessing.pool.RemoteTraceback:
"""
Traceback (most recent call last):
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py", line 121, in worker
    result = (True, func(*args, **kwds))
  File "mp_test.py", line 8, in throw
    raise ValueError()
ValueError
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "mp_test.py", line 27, in <module>
    result.get()
  File "/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/multiprocessing/pool.py", line 657, in get
    raise self._value
ValueError
still alive
received sigterm

因此,当解释器退出时,工作程序会收到SIGTERM信号(但是,行为可能取决于操作系统)。请注意,SIGTERM 可以忽略(例如,您在工作人员中使用的第三方库),因此不能保证在这种情况下您的工作人员实际上会退出。


现在,如果您有长期的工作并且确保可以安全地取消它们(例如,因为它们不执行任何I / O),则可以使用类似的方法进行模拟情况2的行为:

with concurrent.futures.ProcessPoolExecutor() as executor:
    try:
        futures = [executor.submit(foo, user_number, user_id)
                   for user_number, user_id in enumerate(user_ids, start=1)]

        for future in concurrent.futures.as_completed(futures):
            future.result()

    except Exception:
        # abort workers immediately if anything goes wrong
        for process in executor._processes.values():
            process.terminate()
        raise

这会将SIGTERM发送到遇到异常时仍在运行的所有作业,然后引发异常(并等待所有进程完成,因此可以确保它们已停止)。再次,这不是一个正常的退出-当您在I / O期间中断时,它会并且会导致数据丢失或资源悬空。

Python docsterminate方法说:

  

终止该过程。在Unix上,这是使用SIGTERM信号完成的。在Windows上使用TerminateProcess()。请注意,退出处理程序和finally子句等将不会执行。

     

请注意,该进程的后代进程不会终止-它们只会变得孤立。

     

警告

     

如果在关联的进程正在使用管道或队列时使用此方法,则该管道或队列可能会损坏,并且可能无法被其他进程使用。同样,如果进程已获取锁定或信号量等,则终止该进程可能会导致其他进程死锁。