Featured image of post Python的GIL与Asyncio: 理解它们的关系
Python 并发

Python的GIL与Asyncio: 理解它们的关系

了解Python的全局解释器锁 (GIL) 与asyncio并发的关系, 以及何时使用各自的方式

GIL与Asyncio: 不同的抽象层次

Python的全局解释器锁 (GIL) 和asyncio经常在并发相关的讨论中被提及, 但它们实际上工作在完全不同的抽象层。理解它们之间的关系, 是编写高效Python代码的关键。

什么是GIL?

全局解释器锁 (GIL) 是一种互斥锁, 用于保护Python对象的访问, 防止多个线程同时执行Python字节码。你可以把它想象成夜店门口的保安, 一次只允许一个人进入。

关于GIL的重要事实:

  • 它存在于Python解释器层面
  • 它影响的是字节码的执行, 而不是你的逻辑任务
  • 它主要与线程相关
  • 它会在执行一定数量的字节码指令后在线程间切换

什么是Asyncio?

Asyncio是一个并发框架, 允许协程在特定点让出控制权, 从而让其他协程运行。你可以把它想象成一个大家自觉轮流合作的系统。

关于asyncio的重要事实:

  • 它存在于应用层
  • 它影响的是逻辑任务的切换, 而不是字节码的执行
  • 它在单线程内管理并发
  • 它在任务显式使用await让出控制权时进行切换

关键区别: 餐厅厨房的类比

想象一个餐厅厨房:

  • GIL 就像有一条规定: 同一时间只能有一位厨师在厨房。即使你雇了很多厨师 (线程), 每次也只能有一个人在工作。
  • Asyncio 就像只有一位厨师, 但他同时做多道菜, 在合适的时机切换工作 (把A菜放进烤箱, 开始切B菜, 检查C菜是否完成) 。

这两种方式解决的是不同的问题, 甚至可以结合使用。

GIL何时重要 (何时不重要)

GIL主要影响CPU密集型的多线程代码:

  • CPU密集型任务 (如计算, 处理) 会受到GIL的限制——多个线程无法真正并行运行
  • I/O密集型任务 (如网络, 磁盘操作) 通常不受影响, 因为线程在I/O操作时会让出GIL

这也是为什么即使有GIL, 使用线程处理I/O密集型任务依然高效。

Asyncio与GIL的共存

Asyncio通过以下方式绕过了许多GIL相关的问题:

  1. 在单线程中运行 (因此不存在GIL争用)
  2. 主要处理I/O密集型操作, GIL影响较小
  3. 明确控制任务切换的时机

处理CPU密集型任务与GIL

如果你有CPU密集型操作, 可以考虑以下几种方案:

  1. 使用多进程: 每个进程拥有独立的Python解释器和GIL

    from multiprocessing import Process
    
    # 通过多进程绕过GIL
    processes = [Process(target=cpu_intensive_task, args=(data,)) for data in chunks]
    for p in processes:
        p.start()
    
  2. 在asyncio中使用线程池: 适用于需要在asyncio应用中运行CPU密集型任务的场景

    import asyncio
    import concurrent.futures
    
    async def main():
        # 在线程池中运行CPU密集型函数
        loop = asyncio.get_running_loop()
        with concurrent.futures.ThreadPoolExecutor() as pool:
            result = await loop.run_in_executor(pool, cpu_intensive_function, data)
    
  3. 使用内置的asyncio辅助函数: 适用于现代Python

    import asyncio
    
    async def main():
        # 自动使用线程池执行
        result = await asyncio.to_thread(cpu_intensive_function, data)
    

理解GIL与asyncio之间的关系, 有助于你根据具体的并发需求选择合适的方案, 从而编写出更高效的Python应用。

© 2022 - 2026 张欣耕

保留所有权利