繁体   English   中英

Python3.9 多处理导致 MacOS 上的范围错误

[英]Python3.9 multiprocessing results in scoping error on MacOS

我在 iMac(2011,macOS High Sierra,Intel Core i7 2600,Python3.9.2)上运行原始脚本时遇到错误,因此使用以下简单代码重现了行为:

from multiprocessing import Pool
import time


def raise_to_power(number):
   number = number ** power
   return number


if __name__ == '__main__':
   start_time = time.time()
   threads = 8
   power = 10
   numbers = [x for x in range(1,1000000)]
   chunksize, rem = divmod(len(numbers), threads)
   if rem: chunksize += 1
   with Pool(threads) as p:
      Product = p.map(raise_to_power,numbers,chunksize)
   print(time.time() - start_time)

iMac 上的 Output 给了我:

multiprocessing.pool.RemoteTraceback: 
"""
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/pool.py", line 125, in worker
result = (True, func(*args, **kwds))
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/pool.py", line 48, in mapstar
return list(map(*args))
File "/Users/yn/PycharmProjects/test-multiprocessing/test-multiprocessing_thinkpad2.py", line 6, in raise_to_power
number = number ** power
NameError: name 'power' is not defined
"""

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/Users/yn/PycharmProjects/test-multiprocessing/test-multiprocessing_thinkpad2.py", line 18, in <module>
Product = p.map(raise_to_power,numbers,chunksize)
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/pool.py", line 364, in map
return self._map_async(func, iterable, mapstar, chunksize).get()
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/multiprocessing/pool.py", line 771, in get
raise self._value
NameError: name 'power' is not defined

相同的代码在 Thinkpad t420(Linux Mint 20,core i5 2540m,Python3.9.2)上正确运行,执行时间约为 0.6-0.7 秒。

我重写了代码以克服范围错误:

from multiprocessing import Pool
import time
from itertools import product


def raise_to_power(number, power):
   number = number ** power
   return number


if __name__ == '__main__':
   start_time = time.time()
   threads = 8
   power = 10
   numbers = [x for x in range(1,1000000)]
   chunksize, rem = divmod(len(numbers), threads)
   if rem: chunksize += 1
   with Pool(threads) as p:
      Product = p.starmap(raise_to_power,product(numbers,[power]),chunksize)
   print(time.time() - start_time)

现在它在两个系统上都能正常运行(iMac 平均只快 0.15 秒),但我在多处理文档中没有找到关于所描述行为的任何线索。 请你指出来好吗?

让我们看一下您的原始方法,其中未定义power 首先,有几条评论。 您正在调用工人 function multiply_by ,但我也没有看到它在哪里定义。 但是,我确实看到了 function raise_to_power 您应该得到的错误是没有定义multiply_by 所以这有点令人费解。 其次,我看到您正在计算块大小,而不是使用默认的chunksize来执行此操作,这将计算出大约是您计算的大小的 1/4 的值。 更大的块大小意味着更少的 memory 传输(好),但如果处理器不以相同的速率处理任务(坏),则可能导致处理器最终处于空闲状态,在您的情况下,这不太可能发生。 I can see having your own function for calculating the chunksize since the function used by the map method must convert its iterable argument to a list if necessary in order to get its length and if the iterable is very large, this could be very memory-inefficient . 但是您已经将一个range转换为一个列表,因此您没有利用这个机会通过自己的计算来保存 memory。

正如 Mark Satchell 所指出的,对于您的案例,简单的解决方案就是让power成为全球性的。 但是让我们考虑一般情况。 如果您的平台是 Windows 或任何使用spawn创建新进程的平台(我猜测这可能是基于您使用if __name__ == '__main__':管理创建新进程的代码的情况),那么任何代码即在全局 scope 将为每个创建的新进程执行。 对于像power = 10这样的语句,这不是问题。 但是,如果power需要更复杂的代码来初始化其值,那么为进程池中的每个进程一遍又一遍地重新执行此代码将是低效的。 或者考虑一下power是一个非常大的阵列的情况。 在每个子进程的 memory 空间中创建该数组的实例可能成本太高。 然后需要的是共享 memory 中的数组的单个实例。

在创建Pool实例时,有一种机制可以通过使用初始化程序initargs arguments 来为池中的每个子进程初始化全局变量。 通过利用使用自己的块大小计算这一事实,我还进行了额外的更改以保存 memory:

from multiprocessing import Pool
import time


def init_pool(the_power):
    """ Initialize each process's global variable power. """
    global power
    power = the_power

def raise_to_power(number):
   number = number ** power
   return number


if __name__ == '__main__':
   start_time = time.time()
   threads = 8
   power = 10
   FROM = 1
   TO = 1000000
   cnt = TO - FROM
   # changed to a generator expression to save memory:
   #numbers = (x for x in range(FROM, TO))
   # or just:
   numbers = range(FROM, TO)
   chunksize, rem = divmod(cnt, threads)
   if rem: chunksize += 1
   # initialize each pool process with power:
   with Pool(threads, initializer=init_pool, initargs=(power,)) as p:
      Product = p.map(raise_to_power,numbers,chunksize)
   print(time.time() - start_time)

进一步说明

当在使用操作系统调用 spawn 来创建新进程而不是 fork 的平台上创建子进程时,新进程不会继承在父进程中设置的任何全局变量的副本。 相反,执行从程序的顶部开始。 但是因为你有if __name__ == '__main__':保护创建新进程和初始化电源的代码(这是避免递归循环无限地重新创建新进程所必需的),语句power = 10永远不会被执行在子流程中, 当您将power = 10移到if __name__ == '__main__':之外时,即使创建了子流程,它也会被执行。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM