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