【问题标题】:Light persistence in the context of ThreadPoolExecutor in PythonPython 中 ThreadPoolExecutor 上下文中的轻持久化
【发布时间】:2016-12-29 03:01:21
【问题描述】:

我有一些 Python 代码使用 ThreadPoolExecutor 将昂贵的作业分流出来,我想跟踪其中哪些已经完成,这样如果我必须重新启动这个系统,我就不必重做已经完成的东西。在单线程上下文中,我可以只标记我在架子上所做的事情。下面是这个想法在多线程环境中的一个幼稚移植:

from concurrent.futures import ThreadPoolExecutor
import subprocess
import shelve


def do_thing(done, x):
    # Don't let the command run in the background; we want to be able to tell when it's done
    _ = subprocess.check_output(["some_expensive_command", x])
    done[x] = True


futs = []
with shelve.open("done") as done:
    with ThreadPoolExecutor(max_workers=18) as executor:
        for x in things_to_do:
            if done.get(x, False):
                continue
            futs.append(executor.submit(do_thing, done, x))
            # Can't run `done[x] = True` here--have to wait until do_thing finishes
        for future in futs:
            future.result()

    # Don't want to wait until here to mark stuff done, as the whole system might be killed at some point
    # before we get through all of things_to_do

我能摆脱这个吗? documentation for shelve 不包含任何关于线程安全的保证,所以我认为没有。

那么处理这个问题的简单方法是什么?我认为也许坚持done[x] = Truefuture.add_done_callback 会做到这一点,但that will often run in the same thread as the future itself。也许有一种锁定机制可以很好地与 ThreadPoolExecutor 配合使用?对我来说,编写一个休眠然后检查已完成期货的循环似乎更清晰。

【问题讨论】:

    标签: python multithreading shelve


    【解决方案1】:

    虽然您仍在最外层的with 上下文管理器中,但done 搁置只是一个普通的python 对象——它仅在上下文管理器关闭并运行其__exit__ 方法时写入磁盘.因此,由于GIL(只要您使用的是 CPython),它与任何其他 python 对象一样是线程安全的。

    具体来说,done[x] = True 的重新分配是线程安全的/将以原子方式完成。

    需要注意的是,虽然搁置的__exit__ 方法将在 Ctrl-C 之后运行,但如果 python 进程突然结束则不会运行,并且搁置不会保存到磁盘。

    为了防止此类故障,我建议使用基于文件的轻量级线程安全数据库,例如 sqllite3

    【讨论】:

    • 上下文管理器的__exit__ 即使在键盘中断异常的情况下也会被调用,所以我的状态似乎会持续存在。至于线程安全,你是说所有的 Python 对象都是因为 GIL 而线程安全的吗?
    • 并非所有 python 对象总体上都是线程安全的,但是(至少在 CPython 中)您使用的是不包括 I/O 的基本原子操作(分配/重新分配)(写入发生在__exit__),所以你会很安全。
    • 我确实想补充一点,尽管您的代码有点不正确-您不应该调用do_thing,而是将其作为第一个参数传递。此外,您应该将executor.submit 的返回值存储到一个列表中(通常称为futs)。然后,在ThreadPoolExecutor 上下文中,循环访问调用每个对象的result() 方法的列表。这将阻止解释器继续,直到所有任务完成。
    • 谢谢。我已经修复了传递 do_thing 作为 executor.submit 的参数,而不是调用它。如果我使用上下文管理器,是否需要调用每个未来的 result() 方法? documentation for Executor.shutdown() 说:“如果您使用 with 语句,您可以避免显式调用此方法,这将关闭 Executor(等待就像在 wait 设置为 True 时调用 Executor.shutdown() )。”
    • 无论如何,您都应该调用每个未来的 result() 方法,否则它将吞没所有异常。如果在运行提交的函数时出现异常,当您从其对应的未来对象中获取结果时会引发异常。否则,它会默默地失败,你永远不会知道出了什么问题。
    猜你喜欢
    • 1970-01-01
    • 2016-01-31
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多