Python中的GIL和异步Asyncio、Futures

本文深入探讨Python中的同步与异步编程概念,包括Asyncio的使用方法,多线程与多进程的执行策略,以及GIL对Python线程的影响。详细解析了如何在Python中利用异步IO提高程序效率,以及在多核CPU环境下多进程相较于多线程的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 、基本概念

以下概念都是在 Python 环境下

  1. Sync 同步编程
  2. Async 异步 ,是指在外观上看来程序不会等待,而是找出可执行的操作/任务/线程 继续执行
  3. Asyncio 单个主线程 多个不同的任务 task ,这些 future 对象 被 event loop 对象控制,就好像多线程版本的多个线程
  4. GIL 全局解释器锁 Global Interpret Lock
  5. Task 任务
  6. 并发 Concurrency 同一时刻只有一个任务/线程 在执行,只不过会不停的切换这些任务
  7. 并行 Parallelism 多个进程同一个时刻一起执行
二、用法
  • Asyncio
import asyncio
import aiohttp
import time

async def download_one(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            save resp

async def download_all(sites):
    tasks = [asyncio.create_task(download_one(site)) for site in sites]
    await asyncio.gather(*tasks)

def main():
    sites = [
        ...
        ...       
    ]

    asyncio.run(download_all(sites))
    
if __name__ == '__main__':
    main()
  • Futures 多线程 线程池执行
   with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        executor.map(download_one, sites)
  • Futures 多进程执行
   with concurrent.futures.ProcessPoolExecutor(max_workers=int(max_workers)) as executor:
            results = executor.map(open_rebuild_url, df.tolist())

GIL

说到 GIL,首先要明确,GILPython 中 的一个全局解释锁,是 Python 设计解释器 Cpython 的一个技术术语,用于在解释器执行Python 线程时,锁住当前运行的线程,防止其他线程执行。在这个基础上, 你就明白为什么说,Python 是单线程的语言了,因为在GIL 的限制下,只能同时执行一个线程,所谓的多线程实际上是,线程获取和释放 GIL 的切换过程,看起来像 多线程 。

GIL最初,是因为Python的解释器是 Cpython ,GIL 的目的是:

  1. 内存管理的 race condition 资源竞争 ,数据混乱,线程安全的问题
  2. C语言库大部分不是原生线程安全

Python 中 对 GIL 的 释放和获取是 通过 Cpython 解释器的check_interval ,Cpython 会轮询GIL 的状态,每隔一段时间,就会强制释放当前线程锁住的GIL ,给与别的线程执行的机会。

早期的轮询机制是

  1. 100 ticks = 1000bytecodes
  2. Python3 + interval 是 15 ms 轮询一次

Cpython 在 Py3 + 之后,15ms 线程强制释放GIL ,这样对CPU密集型更加的友好,比之前的 bytecodes 要避免很多的切换次数了,但是同一时间仍然只能执行一个线程,所以依旧很慢,多核的多进程,会有CPU0 释放 GIL ,CPU 1,2,3 ,0 竞争线程资源的问题,所以实际上造成了新的问题,线程颠簸 thrashing ,导致效率更低。

所以,多核CPU ,要想快,还是要用 多进程执行 优于 多线程 。这样的意义在于,每个进程都有独立的GIL ,不会发生线程切换的干扰和竞争,实现真正的并行。

那么如果避免 GIL 带来的线程切换的瓶颈呢?

  1. 例如 数据分析 常用库 numpy 的 矩阵运算,是由C实现的Python 库 ,所以不需要cpython 解释器,自然不受 GIL的影响

  2. 绕过Cpython 比如用其他Jpython 等其他实现 ,即,在 Java 代码中执行 Python 代码的方法 ,或者核心(在线程切换造成瓶颈的代码)用C++ 实现按,比如机器学习,AI,人脸识别 等工作的核心代码,基本都是用C++辅助实现的 。

靠脚本语言,去解决CPU密集问题,本身就是一个不切实际的想法,python和c 30倍的性能差距 摆在那里。在实际的工作中,非理论的现实中,很少有靠脚本语言去解决 cpu 密集操作的,而io 密集的高阻塞操作的解决方案,已经有很多了,比如 futures io wait asyncio

<think>好的,用户想了解Python中的异步并发。首先,我需要理清这两个概念的区别联系。并发是一个更广泛的概念,指的是同时处理多个任务,而异步是一种实现并发的方式。Python中常用的并发方式有多线程、多进程异步IO。 接下来,我应该分别解释每个部分。先讲并发,说明它是什么,然后介绍多线程多进程。需要提到GIL(全局解释器锁)对多线程的影响,特别是对于CPU密集型任务,多线程可能不是最佳选择,这时候多进程更适合。而对于IO密集型任务,多线程或异步IO更有效。 然后是异步编程,重点在asyncio库。需要解释事件循环、协程、async/await关键字。要说明异步IO如何在单线程中实现高并发,通过非阻塞IO操作来提升效率。举个例子,比如处理大量网络请求时,异步比多线程更高效,因为避免了线程切换的开销。 还要比较不同方法的适用场景:多进程适合CPU密集型,多线程适合IO密集型但受GIL限制,异步IO适合高IO且支持异步库的情况。可能用户会混淆这些,所以需要明确各自的优缺点。 另外,可能需要提到并发并行的区别,因为有时候这两个术语容易被混用。并发是同时处理多个任务,并行是同时执行多个任务,这在多进程中可以实现,而多线程由于GIL通常不能真正并行。 最后,给出示例代码片段会有帮助,比如使用threading、multiprocessingasyncio的简单例子,让用户更直观地理解不同方法的使用方式。同时提醒一些注意事项,比如异步代码中不要调用阻塞函数,否则会破坏事件循环的效率。 检查是否有遗漏点,比如FutureTask对象在asyncio中的作用,或者如何处理异常取消任务。但作为介绍性质的内容,可能不需要太深入,保持简洁明了即可。确保语言通俗易懂,避免过多技术术语,必要时用比喻帮助理解。</think>在Python中,异步并发是处理多任务的核心概念,但它们的实现方式适用场景不同。让我们逐步分析: 一、并发(Concurrency) 指同时处理多个任务的能力,但不一定是同时执行。Python实现并发的三种主要方式: 1️⃣ 多线程(threading模块) - 适合IO密集型任务(如网络请求、文件读写) - 受GIL(全局解释器锁)限制,无法真正并行执行CPU密集型任务 - 示例: ```python import threading def task(): print(f"Thread {threading.get_ident()} working") threads = [threading.Thread(target=task) for _ in range(3)] for t in threads: t.start() ``` 2️⃣ 多进程(multiprocessing模块) - 适合CPU密集型任务(如数学计算) - 每个进程有独立的Python解释器内存空间 - 示例: ```python from multiprocessing import Process def calculate(): print("Processing in subprocess") if __name__ == "__main__": p = Process(target=calculate) p.start() ``` 3️⃣ 异步IO(asyncio模块) - 单线程实现高并发 - 基于事件循环协程(coroutine) - 需要配合async/await语法使用 二、异步编程(Asynchronous) 指通过非阻塞方式处理任务,核心要素: 1️⃣ 事件循环(Event Loop) - 调度协程的执行 - 监控IO事件 2️⃣ 协程(Coroutine) - 用async def定义的函数 - 通过await挂起执行 3️⃣ 典型模式: ```python import asyncio async def fetch_data(): await asyncio.sleep(1) return "Data" async def main(): task1 = asyncio.create_task(fetch_data()) task2 = asyncio.create_task(fetch_data()) await task1 await task2 asyncio.run(main()) ``` 三、关键差异对比表 | 特性 | 多线程 | 多进程 | 异步IO | |-----------|--------------|--------------|--------------| | 内存占用 | 低(共享内存) | 高(独立内存) | 最低 | | 切换成本 | 中等 | 最高 | 最低 | | 数据共享 | 容易(需同步) | 困难(需IPC) | 容易(单线程) | | 适合场景 | IO密集型 | CPU密集型 | 高IO+异步库支持 | | GIL影响 | 受限制 | 无影响 | 无影响 | 四、选择建议 - 计算密集型 → 多进程 - IO密集型且需要简单同步 → 多线程 - 高并发IO且支持异步库 → asyncio - 混合型任务 → concurrent.futures.ThreadPoolExecutor/ProcessPoolExecutor 五、注意事项 1. 异步编程需要整个调用链都支持async/await 2. 避免在异步代码中混用阻塞IO操作 3. 多进程通信成本较高,尽量使用Queue/Pipe 4. 线程间共享数据需使用Lock等同步机制 现代Python(3.5+)推荐优先考虑asyncio方案,但实际选择需根据具体场景评估。异步编程的学习曲线较高,但合理使用可以显著提升程序的吞吐量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值