news 2026/6/9 22:46:23

Day 25:【99天精通Python】多进程编程 - 榨干CPU的每一滴性能

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 25:【99天精通Python】多进程编程 - 榨干CPU的每一滴性能

Day 25:【99天精通Python】多进程编程 - 榨干CPU的每一滴性能

前言

欢迎来到第25天!

在昨天(多线程)的课程中,我们发现了一个令人沮丧的事实:由于 GIL(全局解释器锁)的存在,Python 的多线程在处理CPU密集型任务(如复杂的数学计算、视频转码、图像处理)时,并没有起到加速作用,甚至可能因为上下文切换而变得更慢。

今天,我们要打破这个枷锁。多进程 (Multiprocessing)允许我们启动多个独立的 Python 解释器进程。每个进程都有自己独立的 GIL 和内存空间,它们可以运行在不同的 CPU 核心上,真正实现并行计算

如果说多线程是"一个工厂里招了多个工人"(还是只有一个车间主任指挥),那么多进程就是"开了好几个分厂"。

本节内容:

  • 进程与线程的区别回顾
  • multiprocessing模块基础
  • 为什么必须写if __name__ == '__main__':
  • 进程间通信 (Queue)
  • 进程池 (Pool) 的高效使用
  • 实战练习:多核并行计算素数

一、多进程基础:Process 类

Python 的multiprocessing模块提供了与threading类似的 API,上手非常容易。

1.1 启动一个进程

importmultiprocessingimporttimeimportosdeftask(name):# os.getpid() 获取当前进程ID# os.getppid() 获取父进程IDprint(f"进程{name}启动 (PID:{os.getpid()}, 父PID:{os.getppid()})")time.sleep(2)print(f"进程{name}结束")if__name__=='__main__':print(f"主进程启动 (PID:{os.getpid()})")# 创建进程p=multiprocessing.Process(target=task,args=("Worker-1",))# 启动p.start()# 等待结束p.join()print("主进程结束")

1.2 重要:Windows 下的注意事项

在 Windows 上使用多进程,必须将启动代码放在if __name__ == '__main__':之下。
原因:Windows 创建进程时会导入父模块,如果不加保护,会导致无限递归创建进程,程序直接崩坏。


二、数据隔离:进程之间不共享全局变量

这是多进程与多线程最大的区别。每个进程都有自己独立的内存空间。

importmultiprocessing# 全局变量money=100defchange_money():globalmoney money=0print(f"子进程修改后的 money:{money}")if__name__=='__main__':p=multiprocessing.Process(target=change_money)p.start()p.join()print(f"主进程的 money:{money}")# 结果依然是 100!子进程改的是它自己那份拷贝。

三、进程间通信 (IPC)

既然内存不共享,那进程间怎么传递数据呢?最常用的方式是队列 (Queue)
注意:这里的 Queue 是multiprocessing.Queue,不是线程的queue.Queue

frommultiprocessingimportProcess,Queuedefproducer(q):print("生产者:放入数据...")q.put("Hello")q.put("World")defconsumer(q):print("消费者:准备取数据...")print(q.get())print(q.get())if__name__=='__main__':# 创建一个共享队列q=Queue()p1=Process(target=producer,args=(q,))p2=Process(target=consumer,args=(q,))p1.start()p2.start()p1.join()p2.join()

四、进程池 (Pool):批量管理进程

如果你有 100 个任务要处理,不可能启动 100 个进程(系统资源会耗尽)。通常我们会创建一个进程池,比如池子里有 4 个进程,它们轮流处理这 100 个任务。

4.1 使用 Pool.map (同步/简单)

frommultiprocessingimportPoolimporttimeimportosdefheavy_work(n):print(f"进程{os.getpid()}正在计算{n}^2")time.sleep(1)# 模拟耗时returnn*nif__name__=='__main__':# 创建包含 4 个进程的池# 默认不填则是 CPU 核心数withPool(4)asp:# map 会自动把任务分发给进程池,并收集结果results=p.map(heavy_work,range(10))print(f"计算结果:{results}")

4.2 使用 Pool.apply_async (异步/灵活)

如果不希望阻塞主进程,或者任务参数复杂,可以使用apply_async

if__name__=='__main__':p=Pool(4)results=[]foriinrange(5):# 异步提交任务,返回一个 result 对象res=p.apply_async(heavy_work,args=(i,))results.append(res)p.close()# 禁止再提交新任务p.join()# 等待所有任务完成# 获取结果final_values=[res.get()forresinresults]print(final_values)

五、实战练习:多进程计算素数

让我们看看多进程在 CPU 密集型任务上到底有多强。
任务:计算 1 到 100,000 之间所有素数的个数。

importtimefrommultiprocessingimportPool,cpu_countdefis_prime(n):ifn<=1:returnFalseforiinrange(2,int(n**0.5)+1):ifn%i==0:returnFalsereturnTruedefcount_primes(start,end):count=0foriinrange(start,end):ifis_prime(i):count+=1returncountif__name__=='__main__':# 任务范围TOTAL_NUMS=200000# --- 单进程版本 ---start_t=time.time()result=count_primes(1,TOTAL_NUMS)print(f"单进程耗时:{time.time()-start_t:.2f}s, 结果:{result}")# --- 多进程版本 ---start_t=time.time()# 将任务拆分成 4 份 (假设 CPU 是 4 核)pool_size=4step=TOTAL_NUMS//pool_size ranges=[]foriinrange(pool_size):start=1+i*step end=1+(i+1)*step ranges.append((start,end))# ranges = [(1, 50000), (50001, 100000), ...]withPool(pool_size)asp:# starmap 支持传多个参数results=p.starmap(count_primes,ranges)total=sum(results)print(f"多进程耗时:{time.time()-start_t:.2f}s, 结果:{total}")

预期结果
在多核 CPU 上,多进程版本的速度通常是单进程的 2-4 倍(取决于核心数)。


六、常见问题

Q1:进程是不是越多越好?

不是。

  1. 开销大:创建和销毁进程比线程消耗大得多。
  2. 上下文切换:如果进程数超过 CPU 核心数太多,CPU 就要频繁在进程间切换,反而降低效率。
    最佳实践:进程池大小通常设置为cpu_count()cpu_count() + 1

Q2:进程间通信还有其他方式吗?

除了Queue,还有Pipe(管道,适合两个进程通信)、Manager(可以创建共享的列表、字典,但速度较慢)。


七、小结

多进程 Multiprocessing

Process 类

数据隔离

进程池 Pool

Process(target=...)

start() / join()

全局变量不共享

通信: Queue

Pool(processes=4)

map / starmap (批量)

apply_async (异步)

关键要点

  1. CPU 密集型任务(计算)首选多进程
  2. I/O 密集型任务(网络)首选多线程
  3. 多进程必须注意if __name__ == '__main__':保护。
  4. 进程间数据默认隔离,需通过Queue传递。

八、课后作业

  1. 文件搜索:编写一个多进程程序,在指定的文件夹(包含大量子文件夹)中搜索包含特定关键词的所有.txt文件。主进程负责汇总所有找到的文件路径。
  2. 图片缩略图生成:假设有一个文件夹里有 100 张高清大图。编写程序使用Process Pool并行地将它们缩小为 100x100 的缩略图。(提示:可以使用Pillow库处理图片,需先pip install pillow)。
  3. 多进程进度条:尝试实现一个功能,主进程显示一个总进度条,反映后台多个进程任务的整体完成情况(使用QueueManager传递进度)。

下节预告

Day 26:网络编程入门 (Socket)- 玩转了单机并发,接下来我们要跨越网络,让两台电脑互相聊天!


系列导航

  • 上一篇:Day 24 - 多线程编程
  • 下一篇:Day 26 - 网络编程入门(待更新)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 17:27:55

天猫TP公司是什么意思?一般提供哪些服务?

在电商行业的发展进程中&#xff0c;天猫平台作为国内领先的电商巨头&#xff0c;吸引了众多品牌和商家的入驻。为了帮助品牌方更好地运营天猫店铺&#xff0c;提高店铺的竞争力和业绩&#xff0c;天猫TP公司应运而生。天猫TP公司凭借其专业的运营能力和丰富的资源优势&#xf…

作者头像 李华
网站建设 2026/6/9 18:44:51

解析USB3.0接口定义引脚说明中的盲埋孔使用技巧

从USB3.0引脚定义看高速PCB设计&#xff1a;盲埋孔为何是信号完整性的“隐形推手”&#xff1f;你有没有遇到过这样的情况&#xff1f;明明严格按照USB3.0规范布线&#xff0c;差分对也做了等长匹配&#xff0c;参考平面也没分割——可测试时眼图就是打不开&#xff0c;误码率居…

作者头像 李华
网站建设 2026/6/9 18:43:43

微服务分布式SpringBoot+Vue+Springcloud社区安全智慧消防管理系统

目录社区安全智慧消防管理系统摘要开发技术源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;社区安全智慧消防管理系统摘要 该系统基于微服务分布式架构&#xff0c;采用SpringBoot、Vue.js和SpringCloud技术栈&#xff0c;旨在构建高效…

作者头像 李华
网站建设 2026/6/9 18:36:36

Blazor Web App 在 IIS 部署的基路径设置

引言 在使用 Blazor Web App 进行开发时&#xff0c;部署到 IIS 服务器是一个常见的选择。然而&#xff0c;许多开发者在部署过程中可能会遇到一些配置问题&#xff0c;特别是在处理基路径&#xff08;Base Path&#xff09;设置时。本文将详细介绍如何正确设置 Blazor Web Ap…

作者头像 李华