keyword:multiprocessing decorator pickle

参考

https://segmentfault.com/q/1010000008907475?utm_source=tag-newest
http://ralph-wang.github.io/blog/2015/02/15/zhuang-shi-qi-yu-duo-jin-cheng-yi-ji-pickle/
https://stackoverflow.com/questions/9336646/python-decorator-with-multiprocessing-fails

multiprocessing是python常用的多进程模块,可以最大化的发挥机器性能。
经常会计算函数运行耗时,用到装饰器。
但在multiprocessing中运行带装饰器的函数时,就会报错,涉及到了Pickle的问题。

根据参考资料,多进程时,对象及数据是需要序列化反序列化传递的。而python常见的序列化方式就是Pickle。由参考链接可以知道,不是所有的对象(及函数)都可以Pickle序列化。Pickle序列化需要满足几个需求,详情文档。其中比较重要的一个就是,被序列化的对象要在模块顶层定义。
例如我们常见的计时装饰器方法如下:

def time_elapse(fn):
    def _wrapper(*args, **kwargs):
        start = time.perf_counter_ns()
        fn(*args, **kwargs)
        print(f"{fn.__name__} cost {(time.perf_counter_ns() - start)/1_000_000_000} s")
    return _wrapper

@time_elapse
def f():
	print("f")

f.__dict__
# 输出为{}

由于f__dict__属性在被装饰器修饰后,并不在顶层,所以无法通过__dict__属性正确反序列化。
此时我们换另一种方式,使用类装饰器,写法如下:

class TimeElapse(object):
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        start = time.perf_counter_ns()
        self.func(*args, **kwargs)
        print(
            f"{self.func.__name__} cost {(time.perf_counter_ns() - start)/1_000_000_000} s")

def f():
	print("f")
f1 = TimeElapse(f)
f1.__dict__
# 输出为{'func': <function __main__.f()>}

由此可知,经过类装饰器的修饰后,__dict__是可以准确的存储并还原信息的,所以Pickle序列化能成功。

结论

multiprocessing的进程操作设计Pickle序列化,而函数装饰器的结构特点导致Pickle序列化失败。通过改用类装饰器,可以解决Pickle序列化失败的问题。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐