报错/wins系统下,Linux系统无报错
def decorator_timer(func):def wrapper(*args,**kwargs):t1 = timeit.default_timer()func(*args, **kwargs)t2 = timeit.default_timer()print(f'{func.__name__}() has been running for "{round(t2-t1,3)}s"...')return funcreturn wrapper
在win系统中使用装饰器装饰子进程的函数时,会有下面报错
Traceback (most recent call last): File “G:/code_workspace/py3_2020/8_MultiThreading/2_multiprocess.py”, line 34, in
work_p.start()File “G:\Anaconda\lib\multiprocessing\process.py”, line 112, in start
self._popen = self._Popen(self)File “G:\Anaconda\lib\multiprocessing\context.py”, line 223, in _Popen
return _default_context.get_context().Process._Popen(process_obj)File “G:\Anaconda\lib\multiprocessing\context.py”, line 322, in _Popen
return Popen(process_obj)File “G:\Anaconda\lib\multiprocessing\popenspawnwin32.py”, line 65, in __init
reduction.dump(process_obj, to_child)File “G:\Anaconda\lib\multiprocessing\reduction.py”, line 60, in dump
ForkingPickler(file, protocol).dump(obj)AttributeError: Can’t pickle local object ‘decorator_timer.
.wrapper’
被装饰器装饰的函数无法多进程原因
python多进程的原理是通过pickle多进程函数名,然后新建一个子进程并在子进程中导入模块后unpickle,通过访问模块的该函数来实现函数在子进程中的运行的。
就是说多进程获取返回值是需要序列化的,但是装饰器是无法序列化的,所以报错。查阅资料找到解决办法为:functools.wraps(func),它可以解除这种束缚,从而变的可序列化。
关于多进程编程中的pickle的一个重点是,对于多进程函数的pickle,只会pickle其函数名,也即f.name属性,然后通过模块的点号访问,因此该函数必须是可以通过模块点号访问到的,这也就是为什么要求被pickle必须被定义在模块的顶层。
问题在于,当一个函数被装饰器修饰过后,根据装饰器的语法规则,实际上是对被装饰器函数复制了一个新的wrapper函数的引用,这时被装饰函数的名称属性就发生了改变,变成了wrapper的名称,更具体的关于装饰器语法的说明可看笔者这篇文章和这篇文章。并且由于wrapper函数是定义在装饰器函数中的,即非模块顶层函数,无法通过点号访问到,因此这时如果直接将被装饰函数用以多进程运行,会报错类似这样的错误”AttributeError: Can’t pickle local object ‘decorator.
导入functools模块中的wraps()方法解决问题
import timeit
import time
import multiprocessing
import functools
import os
def decorator_timer(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
t1 = timeit.default_timer()
func(*args, **kwargs)
t2 = timeit.default_timer()
print(f'{func.__name__}() has been running for "{round(t2-t1,3)}s"...')
return func
return wrapper
@decorator_timer
def work():
for i in range(5):
print(f'working...')
time.sleep(0.2)
print('work_p finished!')
if __name__ == '__main__':
work_p = multiprocessing.Process(target=work,daemon=True)
# work_p.daemon = True
work_p.start()
# 主进程等待1s
time.sleep(0.5)
print('main process finished!')
working...
working...
working...
main process finished!
Process finished with exit code 0
