
本文深入探讨了python多进程编程中一个常见的陷阱:当使用multiprocessing.pool.starmap配合zip函数来传递参数,并且其中一个参数是初始为空的multiprocessing.managers.syncmanager.dict时,可能导致任务无法执行。文章详细解释了zip函数的工作原理,揭示了问题根源,并提供了正确的参数构造方法,以确保多进程任务能有效利用共享数据并返回预期结果。
在Python中,多进程(multiprocessing)是实现并行计算的强大工具。当需要多个进程访问和修改同一份数据时,multiprocessing.managers.SyncManager提供的共享数据结构(如SyncManager.dict)变得尤为重要。然而,在使用这些工具时,一些看似细微的编码习惯可能会导致程序行为异常,甚至无法执行预期任务。
考虑以下使用multiprocessing.Pool进行并行计算的场景,其中尝试通过starmap将任务编号和一个共享字典传递给每个子进程:
import multiprocessing as mp
from multiprocessing.managers import SyncManager
n_cores = mp.cpu_count()
def parallel_fn(job_n, cache):
# 尝试将job_n作为键和值存入共享字典
cache['job_b'] = job_n
return job_n
if __name__=="__main__":
with SyncManager() as manager:
shared_cache = manager.dict()
# 构造starmap的参数
args = list(zip(range(n_cores), shared_cache))
with mp.Pool(n_cores) as pool:
result = pool.starmap(parallel_fn, args)
print(f"Pool return: {result}")
print(f"Shared dict after: {shared_cache}")运行上述代码,我们可能会观察到如下令人困惑的输出:
Pool return: []
Shared dict after: {}预期的结果是result列表中包含n_cores个任务的返回值,并且shared_cache中也应存储了相应的数据。然而,实际输出显示Pool返回了一个空列表,共享字典也保持为空。这意味着parallel_fn函数根本没有被执行。
立即学习“Python免费学习笔记(深入)”;
造成上述问题的原因并非多进程本身,而是zip函数的一个关键特性。zip函数用于将多个可迭代对象打包成一个元组的迭代器,其核心行为是:当任何一个输入的可迭代对象耗尽时,zip函数就会停止生成元素。
在原始代码中,args的构造方式是:
args = list(zip(range(n_cores), shared_cache))
这里,range(n_cores)是一个长度为n_cores的可迭代对象。然而,shared_cache是一个通过SyncManager创建的ProxyDict对象,它在初始化时是空的。当zip函数尝试将range(n_cores)和shared_cache打包时,它会发现shared_cache(作为可迭代对象时,表示其键的迭代器)是空的。根据zip的规则,一旦遇到空的输入可迭代对象,它就会立即停止,因此list(zip(...))的结果就是一个空列表。
我们可以通过一个简单的例子来验证zip的这一行为:
print(list(zip([1, 2, 3], dict()))) # 输出: [] print(list(zip(range(5), []))) # 输出: []
因此,pool.starmap接收到的args列表实际上是空的,导致没有任何任务被分派到子进程执行。
此外,原始代码中parallel_fn内部将键固定为'job_b':cache['job_b'] = job_n。如果我们的目标是让每个任务将其自己的job_n作为键存储,那么这种固定键的方式是不合适的。
要解决这个问题,我们需要确保starmap接收到的args列表包含正确数量的元组,每个元组都包含job_n和共享字典的引用。最直接且推荐的方法是使用列表推导式(list comprehension)来构造args:
import multiprocessing as mp
from multiprocessing.managers import SyncManager
n_cores = mp.cpu_count()
def parallel_fn(job_n, cache):
# 将job_n作为键和值存入共享字典,更符合逻辑
cache[job_n] = job_n
return job_n
if __name__=="__main__":
with SyncManager() as manager:
shared_cache = manager.dict()
# 使用列表推导式构造starmap的参数
# 确保每个任务都能获得job_n和shared_cache的引用
args = [(n, shared_cache) for n in range(n_cores)]
print(f"Arguments for starmap: {args}") # 打印以验证args内容
with mp.Pool(n_cores) as pool:
result = pool.starmap(parallel_fn, args)
print(f"Pool return: {result}")
print(f"Shared dict after: {shared_cache}")在修正后的代码中,args列表将明确包含n_cores个元组,每个元组的第一个元素是任务编号n,第二个元素是shared_cache的引用。这样,starmap就能正确地将任务分发给子进程。
在我的8核机器上运行此代码,输出如下:
Arguments for starmap: [(0, <DictProxy object at 0x...>), (1, <DictProxy object at 0x...>), ..., (7, <DictProxy object at 0x...>)]
Pool return: [0, 1, 2, 3, 4, 5, 6, 7]
Shared dict after: {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7}可以看到,Pool返回了预期的结果,并且shared_cache也成功地存储了每个进程写入的数据。
通过理解zip函数与SyncManager.dict在空状态下的交互行为,并采用正确的参数构造方法,我们可以避免多进程编程中的常见陷阱,确保并行任务能够高效、准确地执行。
以上就是Python多进程编程中zip函数与共享内存字典的常见陷阱解析的详细内容,更多请关注php中文网其它相关文章!
编程怎么学习?编程怎么入门?编程在哪学?编程怎么学才快?不用担心,这里为大家提供了编程速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号