确保酸洗在 ProcessPoolExecutor.submit returns 之前完成

Ensure pickling is complete before ProcessPoolExecutor.submit returns

假设我有以下简单的 class(容易腌制):

import time
from concurrent.futures import ProcessPoolExecutor

class A:
    def long_computation(self):
        time.sleep(10)
        return 42

我希望能够做到这一点:

a = A()

with ProcessPoolExecutor(1) as executor:
    a.future = executor.submit(a.long_computation)

在 Python 3.6.9 上,此操作失败并显示 TypeError: can't pickle _thread.RLock objects。在 3.8.0 上,它会导致无休止地等待获取锁。

什么有效(在两个版本上)是这样的:

a = A()

with ProcessPoolExecutor(1) as executor:
    future = executor.submit(a.long_computation)
    time.sleep(0.001)
    a.future = future

在我看来,executor.submit 阻塞的时间不足以完成 a 的酸洗,并且在酸洗生成的 Future 对象时遇到问题。

我对 time.sleep(0.001) 解决方法不太满意,因为它涉及到一个神奇的数字,我想如果酸洗最终花费更长时间,它很容易失败。我不想睡​​得更安全、时间更长,因为那会是一种浪费。理想情况下,我希望 executor.submit 阻塞,直到可以安全地在 a.

中存储对 Future 对象的引用为止

有更好的方法吗?

再考虑一下,我想到了以下几点:

import pickle

a = A()

with ProcessPoolExecutor(1) as executor:
    a.future = executor.submit(pickle.loads(pickle.dumps(a)).long_computation)

它涉及重复工作,因为 a 被 pickle 两次,但工作正常并确保 Future 对象在任何情况下都不会按照需要被 pickle。

然后我意识到它起作用的原因是它创建了 a 的副本。因此可以通过在提交方法之前简单地(浅)复制对象来避免 pickling 和 unpickling,这确保副本上不存在对 Future 对象的引用:

from copy import copy

a = A()

with ProcessPoolExecutor(1) as executor:
    a.future = executor.submit(copy(a).long_computation)

这比上面的 pickle 循环更快,也更不尴尬,但我仍然对这里的最佳实践感兴趣,所以我会稍等片刻再接受这个答案。