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

Python asyncio库与GIL之间的关系,是否能够解决核心问题?

这是一个非常棒的问题,触及了 Python 并发编程的核心!

简单直接的回答是:是的,GIL 仍然存在,并且依然会影响 Python 的效率,但 asyncio 的聪明之处在于它在很大程度上绕过了 GIL 造成的瓶颈。

为了彻底理解这一点,我们需要弄清楚三个概念:GIL、asyncio 的工作模式,以及它们分别适用于哪种场景。


1. GIL (全局解释器锁) 的真正影响

  • 是什么:GIL (Global Interpreter Lock) 是 CPython 解释器中的一个互斥锁。它确保在任何时刻,只有一个线程能够执行 Python 字节码。
  • 为什么存在:主要是为了简化 CPython 解释器自身的内存管理,防止多个线程同时访问 Python 对象时造成数据竞争和混乱。
  • 影响最大的场景CPU密集型 (CPU-bound) 任务
    • 想象一下,你有 4 个 CPU 核心,你想用 4 个线程来同时进行大量的数学计算。
    • 因为 GIL 的存在,即使你启动了 4 个线程,也只有一个线程能拿到 GIL 并执行 Python 代码。当它执行一小段时间后,会释放 GIL,让另一个线程接手。
    • 这就像一个有 4 个厨师的厨房,但只有一把菜刀 (GIL)。厨师们必须轮流使用这把刀,结果就是 4 个厨师一起工作的效率和一个厨师差不多,甚至因为切换和等待的开销而更慢。
    • 结论:对于纯计算任务,Python 的 threading 模块无法利用多核 CPU 实现真正的并行计算。

2. asyncio (协程) 的工作模式

  • 是什么asyncio 是一个使用 async/await 语法进行单线程并发的库。它不使用多线程。
  • 工作原理协作式多任务 (Cooperative Multitasking)
    • asyncio 在一个线程内运行一个事件循环 (Event Loop)
    • 当一个任务(协程)执行到 await 关键字,并且等待的是一个耗时的 I/O 操作(如网络请求、数据库查询、文件读写)时,它会主动“交出”控制权。
    • 它对事件循环说:“我要等网络数据回来,这需要时间,你先去忙别的吧。”
    • 事件循环就会把这个任务挂起,然后去执行其他已经就绪、可以运行的任务。
    • 当网络数据回来了,事件循环会得到通知,并在合适的时机唤醒之前挂起的任务,让它从 await 的地方继续执行。
  • 影响最大的场景I/O密集型 (I/O-bound) 任务
    • 想象一个服务员(单线程)同时服务多张桌子(多个任务)。
    • 他给 A 桌点完餐(发起一个 I/O 请求),然后不会傻站在那里等厨房出菜。他会立刻去 B 桌点餐,再去 C 桌倒水。
    • 当厨房喊 A 桌的菜好了(I/O 完成),他才会回去给 A 桌上菜。
    • 在这个过程中,服务员(线程)几乎没有闲置的时间,一直在不同的任务之间切换,效率极高。

3. asyncio 与 GIL 的关系:巧妙的规避

现在,我们把两者联系起来:

  1. asyncio 通常在单线程中运行:既然只有一个线程,那么 GIL 根本就不是问题。因为 GIL 是用来锁住多个线程的,单个线程自然不存在竞争 GIL 的情况。asyncio 内部的任务切换是由程序代码(await)主动控制的,而不是由操作系统强制调度的,所以它不需要 GIL 来保护。

  2. asyncio 规避了 GIL 最影响效率的场景:GIL 的主要瓶颈在于它阻止了 CPU 密集型任务的并行。而 asyncio 的设计初衷就是为了优化 I/O 密集型任务。在 I/O 等待期间,线程本来就是空闲的,CPU 并没有在忙于执行 Python 代码。asyncio 正是利用了这段“等待”时间去执行其他任务,从而大大提高了效率。

思考一个极端情况:如果在 asyncio 中运行 CPU 密集型代码会怎样?

这是一个很关键的问题,能帮你彻底理解。

import asyncio
import timeasync def cpu_bound_task():print("CPU 密集型任务开始...")# 这是一个纯计算任务,没有 await,它会一直占用 CPUsum = 0for i in range(10**7):sum += iprint("CPU 密集型任务结束。")async def io_bound_task():print("I/O 任务开始...")await asyncio.sleep(2) # 模拟 I/O 等待print("I/O 任务结束。")async def main():start_time = time.time()await asyncio.gather(cpu_bound_task(),io_bound_task())print(f"总耗时: {time.time() - start_time:.2f} 秒")asyncio.run(main())

运行结果会是:

CPU 密集型任务开始...
CPU 密集型任务结束。
I/O 任务开始...
I/O 任务结束。
总耗时: 3.25 秒  # (大约是 计算时间 + 2秒)

分析

  • cpu_bound_task 开始执行,因为它内部没有 await 来释放控制权,它会霸占整个事件循环,直到它所有的计算都完成。
  • 在此期间,io_bound_task 根本没有机会开始执行。
  • cpu_bound_task 结束后,事件循环才能运行 io_bound_task,然后等待 2 秒。
  • 结论:一个耗时的同步代码(CPU 密集型)会阻塞整个 asyncio 事件循环,让异步的优势荡然无存。

正确做法:如果必须在 asyncio 中处理 CPU 密集型任务,应该使用 run_in_executor 将它扔到单独的线程池或进程池中,这样就不会阻塞事件循环。

# ... (前面的代码)
async def main():loop = asyncio.get_running_loop()start_time = time.time()# 将 CPU 密集型任务放到一个独立的线程池中执行cpu_task = loop.run_in_executor(None, sync_cpu_bound_task) # sync_cpu_bound_task 是一个普通函数io_task = io_bound_task()await asyncio.gather(cpu_task, io_task)print(f"总耗时: {time.time() - start_time:.2f} 秒") # 这时总耗时会更接近于两者中更长的那个时间

总结

特性多线程 (threading)协程 (asyncio)
目标实现并发(在单核上)或并行(在多核上,但受GIL限制)在单线程内实现高并发
工作模式抢占式多任务(操作系统控制)协作式多任务(代码 await 控制)
最佳场景I/O密集型任务,或者需要与阻塞的C库交互海量 I/O 密集型任务(网络、数据库)
GIL的影响严重影响 CPU 密集型任务的性能,因为无法利用多核。基本无影响,因为它在单线程工作,巧妙地规避了GIL的问题。
资源开销线程是操作系统资源,开销较大,不适合开启成千上万个。协程(任务)非常轻量,可以轻松开启成千上万个。

一句话总结:asyncio 并没有解决 GIL,而是选择了一条不同的赛道。它放弃了利用多核并行处理 CPU 密集型任务,专注于在单线程内将 I/O 密集型任务的效率压榨到极致,从而完美地绕开了 GIL 带来的限制。

如果你需要真正的并行计算来压榨多核 CPU,你应该使用 multiprocessing 模块,它会创建独立的进程,每个进程都有自己的 Python 解释器和 GIL。

http://www.lryc.cn/news/580389.html

相关文章:

  • 【鸿蒙】鸿蒙操作系统发展综述
  • Redis 哨兵模式部署--docker版本
  • 个人独创-CV领域快速测试缝合模型实战框架讲解-基础篇-Pytorch必学知识
  • STM32中实现shell控制台(命令解析实现)
  • PyTorch中 item()、tolist()使用详解和实战示例
  • 如何修改Siteground max_execution_time值?
  • 打印界智能助手Print Distributor:日志记录 纸张状态实时看,异常情况秒通知
  • LucidShape 2024.09 最新
  • 顺序栈和链式栈
  • spring加载外部properties文件属性时,读取到userName变量值和properties文件的值不一致
  • 动手实践OpenHands系列学习笔记8:后端服务开发
  • 大数据在UI前端的应用探索:基于用户行为分析的产品优化策略
  • [论文阅读] 软件工程 | 可持续性标志在问答平台中的应用
  • 基于matlab卡尔曼滤波器消除噪声
  • [前缀和+多重背包]3333. 找到初始输入字符串 II
  • JMM--数据原子操作
  • 【深圳大学机器学习】实验一:PCA算法
  • Qt窗口被外部(非Qt内部机制)强制销毁,第二次再重复使用不显示
  • cloudflare配合github搭建免费开源影视LibreTV一个独享视频网站 详细教程
  • vue3 el-input el-select 非空校验
  • 每日学习问题记录
  • DVWA靶场通关笔记-验证码绕过reCAPTCHA(High级别)
  • vue中添加原生右键菜单
  • 【零基础学AI】第24讲:卷积神经网络(CNN)架构设计
  • 【无标题】Go语言中的反射机制 — 元编程技巧与注意事项
  • 3dmax物理材质转换标准材质,物理材质转VR材质,VR材质转标准材质3dmax物理材质转标准材质插件
  • 电脑休眠设置
  • c++ python 共享内存
  • 后端树形结构
  • STM32F103RCTx的PWM输出控制电机