Python协程2

一、Python协程概念

1. ​基本定义

协程是Python中通过async/await语法实现的轻量级并发模型,其本质是异步编程的入口。每个协程函数以async def定义,内部通过await表达式主动暂停执行并交还控制权。

2. ​设计目的

解决传统线程在I/O密集型场景下的性能瓶颈:

  • 高并发场景:1个CPU核可同时管理成千上万个协程(如处理10万+ HTTP请求)
  • 资源高效:协程切换成本仅为线程的1/100(数据来源:Python官方性能报告)
3. ​关键特性
  • 单线程内并发:依赖asyncio事件循环在单线程中调度
  • 非阻塞I/O:通过await将I/O操作交给底层异步驱动(如aiohttp)
  • 生成器继承:协程本质是生成器的增强版(async def → AsyncGenerator

二、Python协程本质

1. ​底层机制
  • 生成器 + 自动恢复async def编译为生成器函数,await等同于yield from
  • 状态机管理:每次await将协程状态保存到堆栈中,恢复时从断点继续
  • 事件循环驱动asyncio.get_event_loop()持续监控协程状态,调度就绪任务
2. ​与传统线程对比
特性线程协程
调度权操作系统内核用户态(Python解释器)
上下文切换寄存器+内核栈(10-20μs)生成器状态(0.1-1μs)
并发模型M:N(多核并行)1:N(单核轮转)
适用场景CPU密集型任务I/O密集型任务
3. ​协程运行流程
import asyncio

async def foo():
    print("Start foo")
    await asyncio.sleep(1)  # 模拟I/O操作
    print("End foo")

async def main():
    await asyncio.gather(foo(), foo())

asyncio.run(main())
  • 事件循环启动asyncio.run()创建循环并运行main()
  • foo()执行:打印"Start foo"后触发await asyncio.sleep(1)
  • 控制权移交:sleep返回Future对象,事件循环切换到其他任务
  • I/O完成回调:1秒后sleep结束,事件循环重新调度foo()继续执行

三、生活化类比:餐厅服务员模型

1. ​场景设定

一家网红餐厅同时服务1000桌客人,每桌客人点菜、等餐、上菜需要不同时间。

2. ​传统线程模式
  • 服务员:每个服务员独立处理一桌客人(类似线程)
  • 问题:服务员需全程跟进客人(如等待厨房出餐),导致空闲等待
  • 结果:1000桌需1000个服务员,人力成本极高
3. ​协程模式
  • 服务员 + 菜单传递员
    • 服务员(协程):接收点菜订单后,将菜单交给后台(await
    • 后台厨师(异步I/O):处理订单并通知服务员(Future完成)
    • 服务员在等待期间可处理新订单(高效利用时间)
  • 优势:1个服务员(主线程)+ N个后台厨师(异步任务)即可服务无限客户
4. ​关键对应关系
餐厅元素协程概念
服务员协程函数(async def)
点菜订单await表达式
后厨处理异步I/O操作(如aiohttp)
菜单传递员事件循环(asyncio)

四、核心语法与API详解

1. ​async/await
  • async:标记函数为协程(生成器函数增强版)
  • await:只能出现在async函数内,用于等待awaitable对象
  • 语法要求
    async def coroutine_name():
        await some_awaitable_object()
2. ​awaitable对象
  • 三种类型
    1. 协程对象async def定义的函数
    2. Future对象:表示异步操作的最终结果(如asyncio.Future()
    3. 任务对象:包装协程的asyncio.Task()(用于并发执行)
3. ​asyncio.wait(fs)
  • 作用:批量等待多个异步任务完成
  • 参数fs可以是Iterable[awaitable]Future
  • 返回值done, pending = asyncio.wait(fs)(两个集合)
  • 示例
    async def fetch(url):
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                return await response.text()
    
    async def main():
        urls = ["https://example.com", "https://google.com"]
        tasks = [fetch(url) for url in urls]
        done, pending = await asyncio.wait(tasks)  # 等待所有任务完成
        print(f"成功获取 {len(done)} 个页面")
4. ​常用API总结
函数/类用途示例
asyncio.run()启动事件循环并运行最高层协程asyncio.run(main())
asyncio.create_task()将协程包装为Task并加入调度队列task = asyncio.create_task(fetch(url))
asyncio.gather()并发执行多个协程并收集结果results = await asyncio.gather(*tasks)
asyncio.sleep()非阻塞式等待(模拟I/O延迟)await asyncio.sleep(1)

五、注意事项与常见误区

  1. 阻塞操作禁忌
    协程中禁止使用同步I/O函数(如time.sleep()requests.get()),会阻塞整个事件循环:

    # ❌ 错误示例(阻塞式sleep)
    async def bad_example():
        await asyncio.sleep(1)  # 正确用法
        time.sleep(1)           # 阻塞事件循环!
  2. CPU密集型任务处理
    协程不适合计算密集型任务,需结合线程池:

    async def compute():
        result = await asyncio.run_in_executor(None, cpu_bound_function())
  3. 异常处理
    需显式捕获协程中的异常,否则会导致未处理的CancelledError

    try:
        await some_task()
    except asyncio.CancelledError:
        print("任务被取消")

六、进阶实战:异步爬虫

import asyncio
import aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = ["https://example.com" for _ in range(100)]
    tasks = [fetch(url) for url in urls]
    responses = await asyncio.gather(*tasks)
    print(f"抓取完成,共获取 {len(responses)} 页数据")

# 运行爬虫
asyncio.run(main())

效果:单线程并发抓取100个页面,耗时约2-3秒(相比同步爬虫的100+秒)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值