0

0

Python多进程编程中zip函数与共享内存字典的常见陷阱解析

霞舞

霞舞

发布时间:2025-11-24 11:27:25

|

483人浏览过

|

来源于php中文网

原创

Python多进程编程中zip函数与共享内存字典的常见陷阱解析

本文深入探讨了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函数用于将多个可迭代对象打包成一个元组的迭代器,其核心行为是:当任何一个输入的可迭代对象耗尽时,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的这一行为:

Mokker AI
Mokker AI

AI产品图添加背景

下载
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也成功地存储了每个进程写入的数据。

关键注意事项与总结

  1. 理解zip函数行为: 在使用zip组合多个可迭代对象时,务必清楚其“最短原则”。如果其中一个可迭代对象为空,结果将是空的。这在构造多进程任务参数时尤其需要注意。
  2. 验证中间数据结构: 在将参数传递给多进程池之前,打印或检查中间变量(如本例中的args)是一个非常好的调试习惯,可以帮助快速定位问题。
  3. 共享数据结构的使用: multiprocessing.managers.SyncManager.dict在作为可迭代对象时,其行为与普通字典类似,即迭代其键。当它为空时,迭代结果也为空。
  4. 明确参数构造: 对于starmap这类需要接收参数元组序列的函数,使用列表推导式或循环来显式构造参数列表通常是更健壮和清晰的做法。
  5. 合理设计字典键: 在共享字典中存储数据时,选择有意义且能区分不同任务的键是最佳实践。将job_n作为键通常比使用固定字符串(如'job_b')更合理。

通过理解zip函数与SyncManager.dict在空状态下的交互行为,并采用正确的参数构造方法,我们可以避免多进程编程中的常见陷阱,确保并行任务能够高效、准确地执行。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

649

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1228

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

1204

2024.04.29

go语言字符串相关教程
go语言字符串相关教程

本专题整合了go语言字符串相关教程,阅读专题下面的文章了解更多详细内容。

193

2025.07.29

c++字符串相关教程
c++字符串相关教程

本专题整合了c++字符串相关教程,阅读专题下面的文章了解更多详细内容。

131

2025.08.07

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

37

2026.03.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 5万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号