当前位置: 首页 > news >正文

为什么子进程总是拿不到数据?聊聊Python多进程里的“隔阂”

免费编程软件「python+pycharm」
链接:https://pan.quark.cn/s/48a86be2fdc0

一个让我在数据清洗项目上翻车的Bug

去年我接了一个数据清洗的活,需要处理100万行日志数据。每行数据要做格式校验、字段提取、类型转换,纯Python跑起来慢得让人抓狂。

我心想:Python多线程有GIL,那我用多进程总行了吧?每个核跑一个进程,4核机器至少提速3倍。

于是我写了这样的代码:

import multiprocessing as mp # 全局配置,所有子进程都要用 CONFIG = {"date_format": "%Y-%m-%d", "max_length": 100} def clean_line(line): # 用CONFIG里的配置清洗单行数据 # ... return cleaned if __name__ == "__main__": with mp.Pool(4) as pool: results = pool.map(clean_line, data)

看起来没问题吧?跑起来之后,诡异的事情发生了:配置在子进程里经常读不到,有时候读到的还是旧值

我当时完全懵了——全局变量不是所有地方都能访问吗?为什么子进程就不行?

后来我才明白:多进程之间是“隔阂”的,每个进程有自己独立的记忆空间,你主进程里的变量,子进程根本看不见。


为什么子进程拿不到你的数据?

要理解这个问题,先记住一句话:进程是独立的

当你用multiprocessing.Process或者Pool创建一个子进程时,Python会做这样几件事:

  1. 启动一个新的Python解释器(相当于重新打开了一个Python程序)

  2. 在新的解释器里,重新执行你的代码(包括导入模块、定义函数、执行全局代码)

  3. 子进程有自己的内存空间,和主进程完全隔开

所以你在主进程里定义的CONFIGmy_listglobal_counter,子进程都看不见。不是Python不让它们共享,而是操作系统根本不允许一个进程随便读另一个进程的内存。

阿里云开发者社区的一篇文章里有一个很直观的例子:

from multiprocessing import Process num = 10 # 主进程的全局变量 def run(): global num num += 1 print(f"子进程里: {num}") if __name__ == "__main__": p = Process(target=run) p.start() p.join() print(f"主进程里: {num}")

运行结果:

子进程里: 11 主进程里: 10

看到了吗?子进程改了num,主进程里的num纹丝不动。子进程拿到的是num的一份拷贝,而不是引用


子进程为什么总是“拿不到数据”?

真实项目里,“拿不到数据”通常表现为这几种情况:

情况1:全局变量在子进程里是“初始值”

就像前面的例子,你在主进程里修改了CONFIG,然后启动子进程,子进程看到的CONFIG可能是最初的值——因为子进程启动时重新执行了模块代码,用的是初始值。

情况2:Windows下更坑

在Windows系统上,multiprocessing启动子进程时会重新导入主模块。这意味着模块顶层所有的代码都会在子进程里再执行一遍。

如果模块顶层有耗时的初始化操作(比如加载大模型、连接数据库),每个子进程都会重复执行一遍,你的程序会慢到怀疑人生。

情况3:进程池(map)里修改不了列表

你可能会这样写:

results = [] # 想把结果收集到这里 def worker(x): results.append(x * 2) # 试图修改全局列表 with mp.Pool(4) as pool: pool.map(worker, range(10)) print(results) # 还是空的!

每个子进程修改的是自己那份results的拷贝,主进程的results从来没变过。


那怎么让子进程“拿到”数据?

好消息是,Python提供了好几种办法来打破这种“隔阂”。

方法1:通过参数传递(最推荐)

不要依赖全局变量,把要用的数据通过参数传进去:

def clean_line(line, config): # 用传进来的config,不用全局的 return cleaned if __name__ == "__main__": CONFIG = {...} with mp.Pool(4) as pool: # 把config作为参数传进去 results = pool.map(lambda x: clean_line(x, CONFIG), data)

这是最简单、最干净的方式。数据从一个地方流向另一个地方,没有隐式的全局依赖。

方法2:用multiprocessing.Queue传递数据

Queue是一个可以在多个进程之间安全传递数据的队列。

from multiprocessing import Process, Queue def worker(q): q.put("子进程处理完了") if __name__ == "__main__": q = Queue() p = Process(target=worker, args=(q,)) p.start() result = q.get() # 主进程收到子进程的消息 print(result)

Queue的原理是:一个进程把数据放进去,另一个进程取出来。数据被序列化后在进程间传递。

方法3:用Manager共享对象

如果你想让多个子进程共享一个列表或字典,可以用Manager

from multiprocessing import Process, Manager def worker(shared_list): shared_list.append("来自子进程的数据") if __name__ == "__main__": manager = Manager() shared_list = manager.list(["主进程的数据"]) p = Process(target=worker, args=(shared_list,)) p.start() p.join() print(shared_list) # ['主进程的数据', '来自子进程的数据']

Manager的原理是:启动一个独立的“管理器进程”,所有对共享数据的操作都通过这个进程来协调。缺点是,因为每次读写都要走进程间通信。

方法4:用shared_memory共享内存(Python 3.8+)

如果数据量大且对性能有要求,可以用shared_memory模块:

from multiprocessing import shared_memory import numpy as np # 主进程创建共享内存 shm = shared_memory.SharedMemory(create=True, size=100) # 子进程通过名字连接到这块内存 shm_b = shared_memory.SharedMemory(name=shm.name)

这是最高效的方式,数据在内存中直接共享,不需要序列化和复制。


一张图理清楚

你想要做的事错误做法正确做法
子进程读主进程的配置用全局变量通过函数参数传递
子进程返回结果给主进程修改全局列表Queue或返回值
多个子进程共享一个计数器用全局变量ManagerValue
多个子进程共享超大数组Manager列表shared_memory

回到开头的Bug

我那个数据清洗项目最后怎么解决的?

我把CONFIG从全局变量改成了函数参数,每个子进程通过参数拿到配置。至于那些需要汇总的结果,我用Queue从子进程收集回来。

虽然代码多写了几行,但再也不会有“子进程拿不到数据”的烦恼了。

记住:多进程之间是“隔阂”的,别指望它们共享记忆。想要传递数据,要么递纸条(参数/Queue),要么用公共黑板(Manager/shared_memory)。

http://www.jsqmd.com/news/1117611/

相关文章:

  • Qwen-Image-Edit-Rapid-AIO:技术架构驱动的极简AI图像编辑解决方案
  • 电话号码地理定位技术:从陌生来电识别到精准地图标记的完整解决方案
  • Java模拟量子密钥分发:从BB84协议理解后量子密码学
  • 74HC32与TM4C129实现2x2键盘矩阵优化方案
  • S1.2 从0到1000用户:独立产品的冷启动实战
  • 今天不学就淘汰:2024新版《律师执业规范》AI条款深度解析,ChatGPT文书输出必须嵌入的6个法定标注项
  • MuleSoft企业级AI编排:安全、可审计的大模型集成实践
  • DS28EC20与PIC18LF26K40嵌入式存储方案解析
  • AI NFT 元数据生成:稀有度规则要先于图片想象力
  • Ubuntu18.04深度学习环境搭建:cuDNN7.5.1与NCCL2.4.2精准安装指南
  • 中文语义相似度实战:从向量表征到业务落地
  • STM32F373VC与KMR221的嵌入式电压管理系统设计
  • 为什么需要开源字体?Liberation Fonts 终极解决方案深度解析
  • Obsidian自动化笔记的终极武器:Templater插件完整使用教程
  • 从缠论新手到量化高手:Chanlun-Pro实战指南
  • 紧急通知:2024Q3起AI生成代码将强制纳入ISO/IEC 27001审计范围!你的项目准备好了吗?
  • 如何实现自然语言到SQL的智能转换:Vanna AI企业级解决方案深度解析
  • STM32G071RB与LTC6903实现精密数字控制振荡器设计
  • 电容对于555测量电池内阻电路的影响
  • 手机HTTPS抓包实战:Burp Suite中间人代理配置与证书安装详解
  • Qwen-Image-Edit-Rapid-AIO:4步极速AI图像编辑的革命性解决方案
  • 从游戏新手到编程高手:CodeCombat如何用奇幻冒险教会你Python和JavaScript
  • GitHub Desktop中文汉化终极指南:3分钟告别英文界面困扰
  • NAFNet图像恢复终极指南:如何用AI魔法让模糊图像重获新生
  • Adobe Downloader:macOS上一键获取Adobe全家桶的终极下载工具
  • 深度解析N_m3u8DL-RE:跨平台流媒体下载器的3种核心架构实现原理
  • MuleSoft企业级AI编排:构建可治理、可审计、可降级的LLM服务总线
  • Path of Building终极指南:打造流放之路完美Build的完整解决方案
  • Magpie窗口超分辨率工具:3步实现游戏画面高清重制
  • 如何轻松获取网页视频资源:开源媒体嗅探工具的完整指南