Python 多进程与多线程:从原理到实践


文章目录

  • Python 多进程与多线程:从原理到实践
  • 前言
  • 一、并发编程基础:进程与线程
    • 1.1 进程(Process)
    • 1.2 线程(Thread)
    • 1.3 进程与线程的关系
  • 二、Python 中的 "特殊情况":GIL 全局解释器锁
    • 2.1 GIL 的工作原理
    • 2.2 GIL 对多线程的影响
  • 三、Python 多线程:threading模块详解
    • 3.1 基本使用:创建线程
    • 3.2 线程同步:解决资源竞争
    • 3.3 守护线程(Daemon Thread)
  • 四、Python 多进程:multiprocessing模块详解
    • 4.1 基本使用:创建进程
    • 4.2 进程间通信(IPC)
    • 4.3 进程池:高效管理多个进程
  • 五、多进程与多线程的核心对比
  • 六、最佳实践与选择建议
  • 总结


前言

在现代软件开发中,充分利用计算机的多核资源、提高程序运行效率是核心需求之一。Python 提供了多进程和多线程两种并发编程方式,但其底层机制和适用场景存在显著差异。本文将从基础原理出发,通过代码示例、对比分析等多样形式,详细解析 Python 多进程与多线程的实现、区别及最佳实践。


一、并发编程基础:进程与线程

在深入 Python 的实现前,我们需要先明确进程和线程的核心概念 —— 这是理解两种并发方式差异的基础。

1.1 进程(Process)

  • 定义:进程是操作系统进行资源分配和调度的基本单位,是程序的一次执行过程。每个进程拥有独立的内存空间、文件描述符、寄存器等资源。
  • 特点
    • 进程间相互独立,一个进程崩溃不会影响其他进程;
    • 进程间通信(IPC)需要通过特定机制(如管道、队列、共享内存);
    • 创建和销毁进程的开销较大(涉及资源分配与释放)。

1.2 线程(Thread)

-定义:线程是进程内的一个执行单元,一个进程可以包含多个线程,线程共享进程的内存空间和资源。

  • 特点
    • 线程间共享进程资源(如全局变量、文件句柄),通信便捷;
    • 线程切换开销小(仅需保存线程上下文);
    • 一个线程崩溃可能导致整个进程崩溃(因共享资源)。

1.3 进程与线程的关系

可以用一个形象的比喻理解:

进程 = 工厂,线程 = 工厂里的工人。

一个工厂(进程)拥有独立的厂房(内存空间)和设备(资源),多个工人(线程)在同一个工厂里协作,共享设备;而多个工厂(进程)则各自独立,需要通过外部通道(IPC)传递物资。


二、Python 中的 “特殊情况”:GIL 全局解释器锁

Python 的多线程行为与其他语言(如 Java、C++)存在显著差异,核心原因是GIL(Global Interpreter Lock,全局解释器锁) 的存在。

2.1 GIL 的工作原理

GIL 是 Python 解释器(如 CPython)的一种互斥锁,其核心作用是:确保同一时刻只有一个线程在解释器中执行字节码。即使在多核 CPU 中,Python 多线程也无法实现真正的 “并行执行”,只能交替执行(并发)。

执行流程:

  • 线程获取 GIL;
  • 执行一定数量的字节码(或遇到 IO 操作);
  • 释放 GIL,让其他线程有机会执行。

2.2 GIL 对多线程的影响

CPU 密集型任务(如数学计算、数据处理):多线程效率甚至可能低于单线程(因 GIL 切换开销);
IO 密集型任务(如网络请求、文件读写):多线程有效(IO 等待时线程释放 GIL,其他线程可执行)。


三、Python 多线程:threading模块详解

Python 的threading模块提供了对线程的封装,支持创建、管理线程及线程同步。

3.1 基本使用:创建线程

创建线程有两种方式:继承threading.Thread类或传入目标函数。

示例 1:通过目标函数创建线程

import threading
import timedef print_numbers(name, delay):"""线程任务:打印数字"""for i in range(5):time.sleep(delay)  # 模拟IO等待(此时释放GIL)print(f"线程{name}{i}")# 创建线程
t1 = threading.Thread(target=print_numbers, args=("T1", 0.5))
t2 = threading.Thread(target=print_numbers, args=("T2", 0.8))# 启动线程
t1.start()
t2.start()# 等待线程结束(主线程阻塞)
t1.join()
t2.join()print("主线程结束")

输出(顺序可能因调度变化):

线程T1:0
线程T2:0
线程T1:1
线程T1:2
线程T2:1
线程T1:3
线程T1:4
线程T2:2
线程T2:3
线程T2:4
主线程结束

3.2 线程同步:解决资源竞争

多个线程共享资源时,可能出现 “资源竞争” 问题(如同时修改全局变量)。需使用锁(Lock) 保证操作的原子性。

示例 2:用 Lock 解决资源竞争

import threading# 共享资源
count = 0
lock = threading.Lock()  # 创建锁def increment():"""线程任务:增加计数"""global countfor _ in range(1000000):# 获取锁(若已被占用则阻塞)with lock:  # with语句自动释放锁,避免死锁count += 1# 创建10个线程
threads = [threading.Thread(target=increment) for _ in range(10)]# 启动所有线程
for t in threads:t.start()# 等待所有线程结束
for t in threads:t.join()print(f"最终计数:{count}")  # 若不加锁,结果可能小于10000000

说明:with lock确保count += 1(非原子操作)在一个线程执行时,其他线程无法修改count,避免数据错误。

3.3 守护线程(Daemon Thread)

守护线程是 “后台线程”,当所有非守护线程结束时,守护线程会被强制终止(如垃圾回收线程)。

示例 3:守护线程演示

import threading
import timedef daemon_task():while True:print("守护线程运行中...")time.sleep(1)def non_daemon_task():time.sleep(3)print("非守护线程结束")# 创建守护线程
daemon_thread = threading.Thread(target=daemon_task)
daemon_thread.daemon = True  # 标记为守护线程# 启动线程
daemon_thread.start()
non_daemon_thread = threading.Thread(target=non_daemon_task)
non_daemon_thread.start()non_daemon_thread.join()  # 等待非守护线程结束
print("主线程结束")  # 此时守护线程被强制终止

输出:

守护线程运行中...
守护线程运行中...
守护线程运行中...
非守护线程结束
主线程结束

四、Python 多进程:multiprocessing模块详解

由于 GIL 的限制,多线程无法利用多核 CPU 处理 CPU 密集型任务。multiprocessing模块通过创建独立进程(每个进程有独立 GIL),实现真正的并行计算。

4.1 基本使用:创建进程

与threading类似,multiprocessing.Process用于创建进程,用法基本一致,但进程间不共享内存。

示例 4:创建多进程

import multiprocessing
import timedef square_numbers(name, numbers):"""进程任务:计算平方"""for num in numbers:time.sleep(0.1)  # 模拟计算耗时print(f"进程{name}{num}^2 = {num**2}")# 数据分块(进程间不共享内存,需显式传递数据)
data1 = [1, 2, 3, 4, 5]
data2 = [6, 7, 8, 9, 10]# 创建进程
p1 = multiprocessing.Process(target=square_numbers, args=("P1", data1))
p2 = multiprocessing.Process(target=square_numbers, args=("P2", data2))# 启动进程
p1.start()
p2.start()# 等待进程结束
p1.join()
p2.join()print("主进程结束")

输出:

进程P1:1^2 = 1
进程P2:6^2 = 36
进程P1:2^2 = 4
进程P2:7^2 = 49
...(并行执行)
主进程结束

4.2 进程间通信(IPC)

进程间不共享内存,需通过队列(Queue) 或管道(Pipe) 通信。

示例 5:用 Queue 实现进程间通信

import multiprocessingdef producer(queue):"""生产者进程:生成数据"""for i in range(5):queue.put(i)  # 向队列放入数据print(f"生产者:放入 {i}")def consumer(queue):"""消费者进程:处理数据"""while True:data = queue.get()  # 从队列获取数据(若为空则阻塞)print(f"消费者:处理 {data}")queue.task_done()  # 标记任务完成if __name__ == "__main__":  # 多进程必须在main模块中启动# 创建进程安全的队列queue = multiprocessing.Queue()# 创建生产者和消费者进程p_producer = multiprocessing.Process(target=producer, args=(queue,))p_consumer = multiprocessing.Process(target=consumer, args=(queue,), daemon=True)# 启动进程p_producer.start()p_consumer.start()# 等待生产者结束p_producer.join()# 等待队列中所有数据被处理queue.join()print("所有数据处理完成")

4.3 进程池:高效管理多个进程

当需要创建大量进程时,使用ProcessPoolExecutor(或multiprocessing.Pool)可避免频繁创建 / 销毁进程的开销。

示例 6:用进程池处理 CPU 密集型任务

from concurrent.futures import ProcessPoolExecutor
import timedef cpu_intensive_task(n):"""CPU密集型任务:计算斐波那契数列"""a, b = 0, 1for _ in range(n):a, b = b, a + breturn aif __name__ == "__main__":# 任务列表(大量计算任务)tasks = [1000000] * 8  # 8个相同的CPU密集型任务# 单进程执行start = time.time()for task in tasks:cpu_intensive_task(task)print(f"单进程耗时:{time.time() - start:.2f}秒")# 进程池执行(使用所有可用CPU核心)start = time.time()with ProcessPoolExecutor() as executor:executor.map(cpu_intensive_task, tasks)  # 并行执行任务print(f"进程池耗时:{time.time() - start:.2f}秒")

输出(因 CPU 核心数不同而异):

单进程耗时:12.34秒
进程池耗时:3.12秒  # 接近单进程的1/4(假设4核CPU)

说明:进程池充分利用多核 CPU,将任务分配到不同核心并行执行,显著提升 CPU 密集型任务效率。


五、多进程与多线程的核心对比

维度多线程(Thread)多进程(Process)
内存共享共享进程内存空间(全局变量可直接访问)独立内存空间(需 IPC 机制通信)
GIL 影响受 GIL 限制,无法并行执行 CPU 密集型任务不受 GIL 限制(每个进程有独立 GIL),可并行利用多核
创建 / 销毁开销开销小(仅切换线程上下文)开销大(需分配独立资源)
容错性一个线程崩溃可能导致整个进程崩溃进程独立,一个崩溃不影响其他
适用场景IO 密集型任务(如网络请求、文件读写)CPU 密集型任务(如数据计算、图像处理)
通信复杂度简单(直接操作共享变量,需注意同步)复杂(需通过 Queue、Pipe 等 IPC 机制)

六、最佳实践与选择建议

  • 根据任务类型选择:
    • IO 密集型(如爬虫、API 服务):优先用多线程(或异步 IO),因线程切换开销小,且 IO 等待时可释放 GIL;
    • CPU 密集型(如数据分析、科学计算):优先用多进程,利用多核并行计算,规避 GIL 限制。
  • 避免过度创建:
    • 线程 / 进程数量并非越多越好,过多会导致调度开销激增;
    • 进程池大小建议设为 CPU 核心数(os.cpu_count()),线程池可略大(如核心数的 5-10 倍)。
  • 混合使用:
    • 复杂场景可结合多进程与多线程(如 “多进程 + 每个进程内多线程”),兼顾并行计算与 IO 效率。
  • 优先使用高级 API:
    • concurrent.futures模块(ThreadPoolExecutor/ProcessPoolExecutor)封装了底层细节,简化代码且更安全。

总结

Python 的多进程与多线程各有优劣,核心差异源于 GIL 和内存模型:

  • 多线程适合 IO 密集型任务,依赖共享内存实现便捷通信;
  • 多进程适合 CPU 密集型任务,通过独立内存和多核并行突破 GIL 限制。

实际开发中,需结合任务特性、资源开销和容错需求选择合适的并发方式,必要时可混合使用以最大化效率。掌握两者的原理与实践,是提升 Python 程序性能的关键技能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93820.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93820.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/93820.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

electron-vite_18Less和Sass共用样式指定

项目中可以封装less公用样式和方法&#xff0c;比如自动以滚动条样式、单行省略号、多行省略号、display:none等&#xff1b;关于additionalData的配置生效,请在main.js中引入一个别的样式或vue组件中使用“<style lang“scss”><style>”找到electron.vite.config…

Python面试题及详细答案150道(71-80) -- 文件操作篇

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

python新工具-uv包管理工具

uv 是一个由 Astral (Ruff 的创建者) 开发的极速 Python 包和项目管理器&#xff0c;用 Rust 编写。它旨在作为传统 Python 包管理工具&#xff08;如 pip、pip-tools、pipx、poetry、pyenv、twine 和 virtualenv 等&#xff09;的替代品&#xff0c;通过其高性能和多功能集成&…

有关spring-ai的defaultSystem与systemMessage优先级

今天在写项目的时候想用nacos随时修改system的prompt&#xff0c;突然发现defaultSystem的优先级比systemMessage高很多&#xff0c;废话我就不说了&#xff0c;看图吧。你觉得证据不够&#xff1f;那这样呢&#xff1f;

#运维 | 前端 # Linux http.server 实践:隐藏长文件名,简短路径 (http://IP:port/别名 ) 访问

如何运行页面为 http://ip:port/名称 1. 准备文件目录 假设文件原始位置&#xff1a; /home/ubuntu/projects/yinran/ckd.html将它移动到子目录并改名为 index.html&#xff1a; mkdir -p /home/ubuntu/projects/yinran/ckd mv /home/ubuntu/projects/yinran/ckd.html \/home/u…

任务管理器不刷新

记录一个小问题&#xff1a; 进入任务管理器之后发现页面不会刷新&#xff0c;性能界面也是一致。解决办法&#xff1a;查看–>更新速度–>正常

2025-08-21 Python进阶9——__main__与lambda

文章目录1 \_\_main\_\_1.1 name 变量1.1.1 当模块作为主程序直接运行时1.1.2 当模块被其他模块导入时1.2 \_\_main\_\_ 的含义1.3 if \_\_name\_\_ \_\_main\_\_1.5 小结2 lambda表达式2.1 基本概念2.2 lambda 函数语法2.3 使用示例2.4 与高阶函数结合使用2.4.1 与 map () 结…

Java:将视频上传到腾讯云并通过腾讯云点播播放

功能需求:传入一个videoFile也就是视频字节流,返回腾讯云点播的视频保存url需要在腾讯云中寻找的配置信息:导入的依赖:<!--腾讯云点播--><dependency><groupId>com.tencentcloudapi</groupId><artifactId>tencentcloud-sdk-java</artifactId&…

Unity3D物理游戏网络同步指南

前言 Unity3D 物理游戏的网络同步是一个复杂但非常核心的话题。要实现一个流畅、公平且可扩展的多人物理游戏&#xff0c;需要深入的理解和精心的设计。 下面我将为你全面解析 Unity3D 物理游戏的网络同步&#xff0c;包括核心概念、主流方案、实现细节以及最佳实践。 对惹&…

Amazon Redshift 访问配置完整指南

概述 Amazon Redshift 是 AWS 提供的云端数据仓库服务,支持多种访问方式。本文将详细介绍如何配置 IAM 权限、使用 AWS 控制台 Query Editor v2,以及通过 SQL Workbench/J 等第三方工具连接 Redshift 集群。 目录 环境准备 IAM 权限配置 Redshift 用户管理 AWS 控制台访问 …

electron-vite_19配置环境变量

前端配罟环境变量主要通过项目根目录下的.env系列文件实现&#xff0c;不同框架(如Vue、React)或构建工具(如Vite、Webpack)的具体操作略有差异&#xff0c;但核心逻辑均为通过环境变量文件区分开发、测试、生产等环境。方案1: 直接在根目录新建.env文件 1.在根目录新建 .env.d…

【python】arange用法

1. NumPy 里的 np.arangeimport numpy as np# 语法 np.arange([start, ]stop, [step, ], dtypeNone)参数说明&#xff1a;start&#xff1a;起始值&#xff08;默认 0&#xff09;stop&#xff1a;终止值&#xff08;不包含这个值&#xff09;step&#xff1a;步长&#xff08;…

力扣1005:k次取反后最大化的数组和

力扣1005:k次取反后最大化的数组和题目思路代码题目 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&…

国产数据库管理工具 CloudDM 2.7.1.0 发布,OceanBase 等数据源支持复杂 SQL 脱敏数据

CloudDM 是 ClouGence 公司推出的面向团队使用的数据库管理工具&#xff0c;支持云上、云下、多云等多种环境&#xff0c;并且提供多达 23 种数据源的支持。CloudDM 还支持数据库 DevOps CI/CD 功能&#xff0c;将用户产品发布流程中数据库发布和程序发布无缝串联起来。 更新亮…

AI大模型实战:用自然语言处理技术高效处理日常琐事

引言在数字化时代&#xff0c;我们每天都会面对大量的琐碎事务&#xff1a;整理会议记录、处理名单数据、撰写学习笔记等等。这些工作不仅耗时&#xff0c;而且容易出错。幸运的是&#xff0c;随着人工智能技术的发展&#xff0c;特别是大语言模型&#xff08;LLM&#xff09;的…

【spring security】为什么要使用userdetailservice

Spring Security UserDetailsService 深度解析&#xff1a;从401到认证成功的完整实现 &#x1f4cb; 目录 问题背景Spring Security认证架构UserDetailsService的作用完整实现过程常见问题与解决方案最佳实践 &#x1f3af; 问题背景 在开发B2B采购平台时&#xff0c;我们…

机器学习中的数据处理技巧

一、Pandas处理丢失数据&#xff08;一&#xff09;判断缺失值​​isnull()函数​​&#xff1a;用于判断数据框&#xff08;DataFrame&#xff09;中各个单元格是否为空&#xff0c;可帮助我们识别出存在缺失数据的单元格位置。&#xff08;二&#xff09;处理缺失值的方法​​…

田野科技“一张皮”,“AI+虚拟仿真”推动考古教学创新发展

文运同国运相牵&#xff0c;文脉同国脉相连。考古不仅关系到我们对古代文化的认知、发掘、保护、利用&#xff0c;关系到考古学学科体系、学术体系、话语体系的建设&#xff0c;更是关系到我国考古学的国际影响力&#xff0c;对增强世界不同地区古代文明的比较研究有着十分重要…

为什么我的UI界面会突然卡顿,失去响应

有操作都应是“非阻塞”的&#xff0c;以确保能随时响应用户的输入。导致主线程阻塞的常见“元凶”主要涵盖五个方面&#xff1a;主线程被“长时间”的同步计算所“阻塞”、单次渲染的界面节点过多或过于复杂、内存中存在“未释放”的巨大对象或“内存泄漏”、响应了“高频率”…

大规模IP轮换对网站的影响(服务器压力、风控)

在当下的互联网环境中&#xff0c;代理IP轮换已经成为爬虫、SEO、数据采集等行业的常见手段。尤其是大规模数据抓取时&#xff0c;通过代理池实现IP轮换&#xff0c;可以有效避免因单一IP请求过于频繁而被目标网站封禁。 然而&#xff0c;大规模IP轮换虽然对采集方有利&#xf…