Python 协程全解析:async/await、asyncio.run、协程 vs 多线程、I/O 密集首选协程

一、什么是协程?

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

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


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

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

✅ 协程的设计初衷是“并发多个任务
协程是为了解决 I/O 密集型的并发问题而设计的。它的优势体现在:

  • 多个任务同时等待 I/O(如文件、网络、数据库)时,
  • 不需要多个线程,也能做到“同时处理多个请求”。

✅ 示例代码1(无意义):

import asyncio

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

asyncio.run(say_hello())
# 只有一个任务,await 的时候事件循环没有其他任务可以调度。
# 所以这个“让出控制权”其实没有带来任何并发优势。

示例代码2(无意义)

import asyncio

async def task(name, delay):
    print(f"{name} 开始")
    await asyncio.sleep(delay)
    print(f"{name} 完成")

async def main():
    # 直接调用协程函数 - 错误!
    await task("任务1", 2)
    await task("任务2", 1)
    await task("任务3", 0.5)

asyncio.run(main())
"""
输出:
任务1 开始
任务1 完成
任务2 开始
任务2 完成
任务3 开始
任务3 完成
"""

示例代码3-正确

import asyncio

async def main():
    # 创建任务但不等待
    task1 = asyncio.create_task(task("任务1", 2))
    task2 = asyncio.create_task(task("任务2", 1))
    task3 = asyncio.create_task(task("任务3", 0.5))
    
    # 等待所有任务完成
    await task1
    await task2
    await task3
asyncio.run(main())
"""
输出结果:
任务1 开始
任务2 开始
任务3 开始
任务3 完成
任务2 完成
任务1 完成
"""

示例代码4-正确

import asyncio

async def main():
    # 一次性收集并执行所有任务
    await asyncio.gather(
        task("任务1", 2),
        task("任务2", 1),
        task("任务3", 0.5)
    )
asyncio.run(main())
"""
输出结果:
任务1 开始
任务2 开始
任务3 开始
任务3 完成
任务2 完成
任务1 完成
"""

三、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())
import asyncio

async def task_one():
    print("任务 1 开始")
    await asyncio.sleep(1)  # 模拟 I/O 操作
    print("任务 1 完成")

async def task_two():
    print("任务 2 开始")
    await asyncio.sleep(0.5)  # 较短的等待
    print("任务 2 完成")

async def main():
    # 错误方式:顺序执行
    print("\n=== 顺序执行(错误方式)===")
    await task_one()
    await task_two()  # 在 task_one 完成后才开始
    
    # 正确方式:并发执行
    print("\n=== 并发执行(正确方式)===")
    task1 = asyncio.create_task(task_one())
    task2 = asyncio.create_task(task_two())
    await task1  # 等待但不阻塞
    await task2

asyncio.run(main())

执行结果:
=== 顺序执行(错误方式)===
任务 1 开始
任务 1 完成
任务 2 开始
任务 2 完成

=== 并发执行(正确方式)===
任务 1 开始
任务 2 开始
任务 2 完成
任务 1 完成

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

对比项协程(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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值