0

0

如何理解Python的并发与并行?

夜晨

夜晨

发布时间:2025-09-05 23:25:02

|

323人浏览过

|

来源于php中文网

原创

答案:Python中并发指任务交错执行,看似同时运行,而并行指任务真正同时执行;由于GIL限制,多线程无法实现CPU并行,仅适用于I/O密集型任务,而真正的并行需依赖multiprocessing或多核支持的底层库。

如何理解python的并发与并行?

理解Python的并发与并行,核心在于区分“看起来同时进行”和“实际同时进行”。并发(Concurrency)指的是系统能够处理多个任务,这些任务可能在同一时间段内交错执行,给人的感觉是它们在同时运行,但实际上,在任何一个精确的瞬间,处理器可能只在处理一个任务。而并行(Parallelism)则意味着多个任务或任务的不同部分在同一时刻被多个处理器核心或处理单元实际地同时执行。在Python的世界里,由于全局解释器锁(GIL)的存在,这两者之间的界限和实现方式显得尤为特殊和重要。

理解这个问题,我们首先要明确Python在处理多任务时的几种主要策略,以及它们各自的适用场景和局限性。在我看来,很多人初学时会混淆线程(threading)和进程(multiprocessing)的用途,甚至对异步(asyncio)的工作机制一知半解,这往往导致在性能优化上走了不少弯路。

Python的并发策略:线程与异步IO

当谈到并发,Python主要提供了两种内建机制:多线程(

threading
模块)和异步IO(
asyncio
模块)。这两种方式都致力于让程序在面对大量I/O密集型任务时,不至于因为等待外部资源(如网络请求、文件读写)而完全阻塞。

多线程,从操作系统的角度看,确实创建了多个执行流。每个线程共享相同的内存空间,这使得数据共享相对容易,但也带来了复杂的同步问题。然而,在Python中,由于GIL的存在,即便我们启动了多个线程,它们也无法真正地在多个CPU核心上并行执行CPU密集型任务。GIL保证了在任何时刻,只有一个线程能够持有Python解释器的控制权。这意味着,如果你的任务是计算密集型的,多线程并不会带来性能上的提升,甚至可能因为线程切换的开销而略有下降。我个人认为,多线程在Python里最适合的场景是那些I/O密集型任务,因为当一个线程在等待I/O操作完成时(比如等待网络响应),它会主动释放GIL,让其他线程有机会运行。这就像一家餐厅里,虽然只有一个厨师(GIL),但他可以在等待烤箱里的蛋糕(I/O)时,去处理另一份沙拉(另一个线程的CPU任务)。

立即学习Python免费学习笔记(深入)”;

异步IO,以

asyncio
为代表,则是另一种完全不同的并发模型。它基于事件循环(event loop)和协程(coroutines),通过单线程协作式多任务的方式实现并发。简单来说,你的代码会明确地“告诉”解释器,当某个操作(通常是I/O操作)需要等待时,它可以暂停当前任务,去执行其他已经准备好的任务,等到等待的操作完成后再回来继续。这就像一个高效的厨师,在等待烤箱里的蛋糕时,会主动去切菜、备料,而不是傻傻地站着。
asyncio
的优势在于其极高的并发效率和资源利用率,尤其适合处理成千上万个并发连接的场景,比如Web服务器、爬虫等。它的缺点是需要代码以
async/await
的方式编写,并且需要所有涉及的库都支持异步操作,否则就可能遇到阻塞。

Python的全局解释器锁(GIL)到底意味着什么,它如何影响并发?

Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个在CPython(Python最常用的实现)中存在的机制,它确保在任何时间点,只有一个线程在执行Python字节码。这听起来有点反直觉,尤其是在多核处理器普及的今天。但它的存在有其历史原因和技术考量,主要是为了简化CPython的内存管理,并使C语言扩展更容易集成。

在我看来,GIL是理解Python并发和并行之间差异的关键。它直接导致了Python的多线程无法实现真正的并行计算,即无法利用多核CPU的优势来加速CPU密集型任务。当你的Python程序启动多个线程去执行大量计算时,它们实际上是在争夺GIL,每次只有一个线程能获得执行权。这就像你有很多工人(线程),但他们都挤在一个狭窄的门口(GIL),一次只能通过一个人。这样一来,增加工人数量并不会让工作完成得更快,反而可能因为争抢和协调而降低效率。

然而,GIL并非一无是处,它在I/O密集型任务中表现得并不那么“坏”。当Python线程执行I/O操作(如读写文件、网络通信)时,它会主动释放GIL,允许其他线程获取GIL并执行Python代码。这意味着,如果你的程序大部分时间都在等待外部响应,那么多线程仍然可以有效地提高程序的吞吐量。举个例子,一个下载文件的线程在等待网络数据时释放GIL,另一个线程就可以去处理用户界面更新,或者发起另一个网络请求。这正是

threading
模块在I/O密集型场景下依然有其价值的原因。但需要注意的是,这种“并发”仍然不是“并行”,因为CPU核心并没有被多个Python线程同时利用。

Rose.ai
Rose.ai

一个云数据平台,帮助用户发现、可视化数据

下载

什么时候应该用
threading
,什么时候用
asyncio
,它们有什么本质区别?

选择

threading
还是
asyncio
,这往往是开发者在面对并发任务时的一个常见困惑。我个人觉得,这两种技术各有侧重,理解它们的本质区别能帮助我们做出更明智的决策。

threading
(多线程)更适合那些:

  1. I/O密集型任务:如网络请求、文件读写、数据库操作等,这些操作在等待时会释放GIL,允许其他线程执行。
  2. 现有阻塞API的集成:如果你需要与大量使用阻塞式I/O的第三方库或系统API交互,
    threading
    可能是更直接、更简单的选择,因为你可以直接将阻塞调用放在一个单独的线程中。
  3. 少量并发任务:对于少量、独立的并发任务,
    threading
    的实现通常更简单直观。

asyncio
(异步IO)则更适合:

  1. 高并发I/O密集型任务:当你的应用需要同时处理成千上万个并发连接时(例如Web服务器、高性能爬虫),
    asyncio
    的非阻塞、事件驱动模型能提供更高的效率和更低的资源消耗。
  2. 需要精细控制任务调度
    asyncio
    的协程模型允许你对任务的暂停和恢复有更细粒度的控制,这对于构建复杂的异步逻辑非常有用。
  3. 新的项目或生态系统:如果你的项目从一开始就设计为异步,并且使用的库都支持
    async/await
    语法,那么
    asyncio
    会是一个非常强大的选择。

本质区别在于它们的并发模型

  • threading
    是基于操作系统的抢占式多任务
    :操作系统负责线程的调度和切换,它可以在任何时候中断一个线程去执行另一个线程。线程是重量级的,创建和切换开销相对较大。
  • asyncio
    是基于单线程的协作式多任务
    :它在单个OS线程中运行,通过一个事件循环来调度协程。协程需要明确地通过
    await
    关键字“让出”控制权,才能让事件循环去执行其他任务。协程是轻量级的,切换开销极小。

在我看来,

threading
更像是“被动”的并发,它依赖于GIL的释放和操作系统的调度;而
asyncio
则是“主动”的并发,它要求开发者在代码层面明确地管理任务的切换。

如何在Python中实现真正的并行计算?
multiprocessing
是唯一的选择吗?

要在Python中实现真正的并行计算,即充分利用多核CPU的优势,

multiprocessing
模块是目前最直接、最常用的原生解决方案。它通过创建新的进程来绕过GIL的限制。每个新进程都有自己独立的Python解释器和内存空间,因此它们各自拥有一个GIL,互不影响,从而实现了真正的并行执行。

multiprocessing
的工作原理和适用场景
multiprocessing
模块提供了
Process
类,可以像使用
threading.Thread
一样创建和启动进程。它的核心思想是“以空间换时间”,通过复制父进程的内存空间(或在Unix-like系统上使用写时复制,copy-on-write)来创建子进程。这使得每个进程都能独立运行CPU密集型任务,互不干扰。

  • 优点
    • 绕过GIL:实现真正的并行计算,充分利用多核CPU。
    • 进程隔离:不同进程之间内存独立,避免了多线程中常见的共享数据竞争问题(但也带来了进程间通信的复杂性)。
  • 缺点
    • 资源开销大:创建和管理进程比线程更消耗系统资源(内存、CPU)。
    • 进程间通信(IPC)复杂:由于内存隔离,进程间需要通过队列(
      Queue
      )、管道(
      Pipe
      )、共享内存等机制进行通信,这比线程间直接共享数据要复杂。
    • 数据序列化:在进程间传递数据时,通常需要进行序列化和反序列化,这会带来额外的开销。

multiprocessing
是唯一的选择吗? 从Python原生库的角度看,
multiprocessing
确实是实现单机多核并行计算的“主力军”。但如果把视野放宽,还有其他一些方式可以实现或辅助实现并行计算:

  1. C/C++扩展(如NumPy、SciPy):许多高性能的Python库(尤其是科学计算领域)底层是用C、C++或Fortran实现的。这些底层代码在执行时可以释放GIL,从而允许底层的C代码在多个CPU核心上并行运行,即使Python解释器本身只有一个GIL。当我们使用NumPy进行矩阵运算时,它内部的C代码可以并行执行,这正是Python在数据科学领域表现出色的一个重要原因。
  2. JIT编译器(如PyPy、Numba):一些替代的Python实现(如PyPy)或库(如Numba)通过即时编译(JIT)技术,可以将Python代码转换为更高效的机器码,有时甚至可以绕过GIL或提供更优化的并行执行能力。Numba的
    @jit(nopython=True, parallel=True)
    装饰器就是一个例子,它可以将Python循环转换为并行执行的机器码。
  3. 分布式计算框架(如Dask、Ray、Apache Spark):对于需要跨多台机器甚至集群进行并行计算的场景,这些框架提供了更高层次的抽象。它们通常会在底层使用进程或更复杂的调度机制来管理任务,将计算分布到多个节点上。虽然它们不是直接替代
    multiprocessing
    的单机并行方案,但它们是解决更大规模并行计算问题的答案。

总的来说,对于大多数Python开发者而言,当需要利用多核CPU进行CPU密集型任务时,

multiprocessing
是首选且最直接的工具。但了解其他方案,尤其是在科学计算和大数据领域,可以帮助我们更好地选择适合特定问题的并行化策略。

热门AI工具

更多
DeepSeek
DeepSeek

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

豆包大模型
豆包大模型

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

WorkBuddy
WorkBuddy

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

腾讯元宝
腾讯元宝

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

文心一言
文心一言

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

讯飞写作
讯飞写作

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

即梦AI
即梦AI

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

ChatGPT
ChatGPT

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

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

410

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

638

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

362

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

263

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

630

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

562

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

671

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

618

2023.09.22

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
swoole进程树解析
swoole进程树解析

共4课时 | 0.2万人学习

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

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