Python协程详解:async/await、asyncio.run应用,协程与多线程对比,I/O密集型任务首选协程之路

一、什么是协程?

协程(Coroutine)是一种轻量级的用户态线程,它由程序自己控制什么时候挂起、什么时候恢复,不依赖操作系统线程调度。

在 Python 中,协程是通过 async/await 实现的异步编程模型,依赖于事件循环(asyncio)进行调度。


二、async/await 的作用是什么?

  • async def:声明一个异步函数(协程函数),调用它返回一个协程对象。
  • await:在协程中挂起当前执行,等待另一个协程完成后再继续。
  • ✅ 示例代码:

    import asyncio
    
    async def say_hello():
        await asyncio.sleep(1)
        print("Hello")
    
    asyncio.run(say_hello())
    

    三、asyncio.run() 的作用是什么?

    ✅ 简洁定义:
    asyncio.run() 是运行协程程序的推荐入口。

    ✅ 它做了什么?

  • 创建事件循环
  • 执行协程直到完成
  • 自动关闭事件循环
  • 返回协程的运行结果
  • ✅ 示例:

    import asyncio
    
    async def main():
        await asyncio.sleep(1)
        return "任务完成"
    
    result = asyncio.run(main())
    print(result)
    

    四、async 函数必须包含 await 吗?

    ❌ 答案:不是必须,但通常推荐。

    async def hello():
        print("Hi")  # 没有 await 也合法
    
    asyncio.run(hello())
    

    但这样写就失去了异步的意义,等同于普通函数。

    五、协程调用协程是否必须 await?

    ✅ 是的!调用异步函数必须加 await 才会执行,否则只会返回一个协程对象,但不会运行。

    ❌ 错误示例:

    async def inner():
        print("Inner")
    
    async def outer():
        inner()  # ❌ 没有 await,不会执行!
    
    asyncio.run(outer())
    

    ✅ 正确写法:

    async def inner():
        print("Inner")
    
    async def outer():
        await inner()  # 👍 正确执行
    
    asyncio.run(outer())
    
    

    六、协程和多线程的区别是什么?

    对比项 协程(asyncio) 多线程(threading)
    并发模型 协作式 抢占式
    本质 单线程 多线程
    是否阻塞 非阻塞(主动让出) 阻塞(由操作系统调度)
    适合场景 I/O 密集 I/O 或部分 CPU 密集
    性能消耗 低(轻量级,切换开销小) 高(线程切换与上下文开销大)
    是否需要加锁 通常不需要 需要(避免数据竞争)

    七、I/O 密集任务是否首选协程?

    ✅ 是的!

    协程非常适合处理 I/O 密集场景,比如:

  • 网络请求
  • 数据库访问
  • 文件读写
  • WebSocket 通信

  • ✅ 实例对比:并发请求 5 个网页

    1. 同步方式(低效)

    import requests
    import time
    
    urls = ['https://httpbin.org/delay/1'] * 5
    
    start = time.time()
    for url in urls:
        requests.get(url)
    print("同步耗时:", time.time() - start)
    
    ## 2. 异步方式(高效)
    ```python
    import asyncio
    import aiohttp
    import time
    
    urls = ['https://httpbin.org/delay/1'] * 5
    
    async def fetch(session, url):
        async with session.get(url) as response:
            return await response.text()
    
    async def main():
        async with aiohttp.ClientSession() as session:
            tasks = [fetch(session, url) for url in urls]
            await asyncio.gather(*tasks)
    
    start = time.time()
    asyncio.run(main())
    print("异步耗时:", time.time() - start)
    

    ✅ 异步方式可大大缩短总耗时,支持高并发请求。

    八、总结:协程的使用建议

    场景 是否推荐协程
    网络请求(爬虫、API) ✅ 强烈推荐
    数据库访问(async 驱动) ✅ 推荐
    文件异步读写 ✅ 推荐
    高并发定时任务 ✅ 推荐
    CPU 密集型(图像处理等) ❌ 不推荐,建议用多进程

    📌 补充:为什么 Jupyter Notebook 中不能直接使用 asyncio.run()

    在 Jupyter Notebook(或 IPython)中,事件循环已经自动运行,用于支持 await 表达式的直接运行。因此:

    await some_coroutine()
    # 在 Notebook 中是合法的,不需要放入 async def 函数中,也不需要 asyncio.run()。
    

    ❌ 如果你强行使用 asyncio.run() 会报错:

    RuntimeError: asyncio.run() cannot be called from a running event loop
    

    ✅ 正确的解决方法(推荐):

    方法 1:直接使用 await(Jupyter 专属)

    import asyncio
    
    async def say_hello():
        await asyncio.sleep(1)
        print("Hello")
    
    await say_hello()  # ✅ Jupyter 中可以直接这样写
    

    方法 2:使用 nest_asyncio 兼容运行

    import nest_asyncio
    import asyncio
    
    nest_asyncio.apply()  # 允许嵌套事件循环
    
    async def say_hello():
        await asyncio.sleep(1)
        print("Hello")
    
    asyncio.run(say_hello())  # ✅ 现在也能用了
    

    ✅ 总结:

    环境 是否能用 asyncio.run() 推荐方式
    普通 Python 脚本 ✅ 可以 asyncio.run()
    Jupyter Notebook ❌ 默认不行 直接使用 awaitnest_asyncio

    作者:Takoony

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python协程详解:async/await、asyncio.run应用,协程与多线程对比,I/O密集型任务首选协程之路

    发表回复