[python學習筆記] - multiprocessing模塊執行帶裝飾器的方法報錯及解決方式

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序列化失敗的問題。

發佈了41 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章