Python
基础语法
01概念
02安装
03变量
04字符串
05数
06常量与注释
07列表
08元组
09if语句
10字典
11集合
12复合数据类型对比
13推导式
14用户输入
15while循环
16函数
17类
18面向对象编程
19文件操作
20异常处理
21日期和时间
22魔术方法
23内置函数
24线程
25并发&并行
26正则表达式
27迭代器
28装饰器
29生成器
30上下文管理器
31函数式编程
32闭包
33解包
34工具库
35连接关系型数据库
36虚拟环境
37异步编程
网络爬虫
01urllib库[了解]
02requests库
03数据交换格式
04解析库
05lxml
06Beautiful Soup
07Xpath语法
08动态网页的处理
-
+
首页
37异步编程
异步编程是一种**非阻塞的编程范式**,核心思想是在程序等待某个操作(如网络请求、文件 IO、数据库查询等)完成时,不阻塞整个程序的执行,而是转去处理其他任务,待等待的操作完成后再回来继续处理结果。这种模式能极大提高**IO 密集型任务**的执行效率,是现代高性能网络服务、数据采集等场景的核心技术。 ## 区别 要理解异步编程,首先需要对比传统的**同步编程**: | **维度** | **同步编程** | **异步编程** | | ---------- | ------------------------------------------------ | ------------------------------------------------- | | 执行方式 | 任务按顺序执行,前一个任务完成后才开始下一个 | 任务无需等待前一个完成,可在等待时切换到其他任务 | | 阻塞问题 | 遇到 IO 操作(如网络请求)时,程序会**阻塞等待** | 遇到 IO 操作时,**不阻塞**,转去执行其他就绪任务 | | 资源利用率 | 低(CPU 在 IO 等待时闲置) | 高(CPU 在 IO 等待时处理其他任务) | | 适用场景 | 简单逻辑、CPU 密集型任务(如数学计算) | IO 密集型任务(如网络请求、文件读写、数据库操作) | **直观例子**: - 同步模式:你去餐厅点餐后,站在窗口一直等待餐品做好,期间什么都不做。 - 异步模式:你点餐后拿到取餐号,去旁边看手机,餐品做好后服务员喊号,你再过去取餐(等待期间可做其他事)。 ## 概念 异步编程的实现依赖多个关键概念,不同语言(如 Python、JavaScript、Go)的术语可能不同,但核心逻辑一致: **1. 协程(Coroutine)** 协程是异步编程的**基本执行单元**,可以理解为 “可暂停、可恢复的函数”。 - 与线程 / 进程不同,协程是**用户态**的轻量级 “任务”,切换由程序(而非操作系统)控制,开销极小(几乎无系统调用)。 - 协程可以在执行到某个节点(如等待 IO)时**主动暂停**(释放 CPU),并在条件满足时**恢复执行**。 在 Python 中,协程通过 \_async def\_ 定义,用 \_await\_ 关键字标记需要暂停等待的操作。 **2. 事件循环(Event Loop)** 事件循环是异步程序的**核心调度器**,负责管理所有协程的执行顺序: - 维护一个 “就绪任务队列”,不断从队列中取出协程执行。 - 当协程执行到 \_await\_ 时(需要等待 IO),将其暂停并放入 “等待队列”,同时从就绪队列取新任务执行。 - 当 “等待队列” 中的任务完成(如 IO 操作结束),将其重新加入 “就绪队列”,等待下次调度。 可以理解为事件循环是 “餐厅经理”,负责安排服务员(协程)的工作顺序,确保没有服务员闲着。 **3. Future/Promise** 表示**一个尚未完成的异步操作的结果**(“未来的结果”)。 - 当发起一个异步 IO 操作(如网络请求)时,会立即返回一个 \_Future\_ 对象,代表这个操作 “将来可能的结果”。 - 事件循环会监听 \_Future\_ 的状态,一旦操作完成(成功 / 失败),就触发对应的回调或恢复等待它的协程。 **4. 任务(Task)** 在 Python 中,\_Task\_ 是对协程的**封装**,用于将协程加入事件循环并管理其生命周期。 - 任务会自动将协程注册到事件循环,由事件循环调度执行。 - 支持并行执行多个任务(通过事件循环的调度实现 “伪并行”)。 ## 异步编程实现 Python 3.4 引入标准库 \_asyncio\_,正式支持异步编程,3.5 新增 \_async/await\_ 语法(简化协程定义),使其易用性大幅提升。 **1. 定义协程:\_async def\_** 用 \_async def\_ 定义的函数是协程函数,调用后返回一个协程对象(而非直接执行)。 ```python import asyncio # 定义协程函数 async def async_task(name, delay): print(f"任务 {name} 开始,等待 {delay} 秒") # 模拟IO操作(必须用await等待异步操作) await asyncio.sleep(delay) # asyncio.sleep是异步版本的sleep print(f"任务 {name} 完成") return f"结果:{name}" ``` **2. 运行协程:事件循环** 协程不能直接调用(\_async_task("A", 1)\_ 仅返回协程对象),必须通过事件循环运行: ```python import asyncio # 定义协程函数 async def async_task(name, delay): print(f"任务 {name} 开始,等待 {delay} 秒") # 模拟IO操作(必须用await等待异步操作) await asyncio.sleep(delay) # asyncio.sleep是异步版本的sleep print(f"任务 {name} 完成") return f"结果:{name}" async def main(): # 运行单个协程 result = await async_task("A", 1) print(result) # 启动事件循环并运行main协程 asyncio.run(main()) ``` 输出结果: ```txt 任务 A 开始,等待 1 秒 任务 A 完成 结果:A ``` \_asyncio.run(coro)\_ 是 Python 3.7+ 新增的简化接口,自动创建事件循环、运行协程、最后关闭循环。 **3. 并行执行多个任务:\_create_task()\_** 通过 \_asyncio.create_task()\_ 将协程包装为任务,实现多个任务并行执行: ```python import asyncio # 定义协程函数 async def async_task(name, delay): print(f"任务 {name} 开始,等待 {delay} 秒") # 模拟IO操作(必须用await等待异步操作) await asyncio.sleep(delay) # asyncio.sleep是异步版本的sleep print(f"任务 {name} 完成") return f"结果:{name}" async def main(): # 创建两个任务(立即加入事件循环调度) task1 = asyncio.create_task(async_task("A", 1)) task2 = asyncio.create_task(async_task("B", 2)) print("等待所有任务完成...") # 等待两个任务完成并获取结果 result1 = await task1 result2 = await task2 print(result1, result2) asyncio.run(main()) ``` 输出结果: ```txt 等待所有任务完成... 任务 A 开始,等待 1 秒 任务 B 开始,等待 2 秒 任务 A 完成 任务 B 完成 结果:A 结果:B ``` **关键**:两个任务总耗时≈2 秒(最长任务的耗时),而非 1+2=3 秒,体现了异步并行的优势。 **4. 批量等待任务:\_gather()\_** 当任务数量较多时,用 \_asyncio.gather()\_ 批量等待多个任务,返回结果列表: ```python import asyncio # 定义协程函数 async def async_task(name, delay): print(f"任务 {name} 开始,等待 {delay} 秒") # 模拟IO操作(必须用await等待异步操作) await asyncio.sleep(delay) # asyncio.sleep是异步版本的sleep print(f"任务 {name} 完成") return f"结果:{name}" async def main(): # 创建3个任务 tasks = [ async_task(f"任务{i}", i) for i in range(1, 4) # 延迟1s、2s、3s ] # 批量等待所有任务,返回结果按任务顺序排列 results = await asyncio.gather(*tasks) print("所有结果:", results) asyncio.run(main()) ``` 输出结果: ```txt 任务 任务1 开始,等待 1 秒 任务 任务2 开始,等待 2 秒 任务 任务3 开始,等待 3 秒 任务 任务1 完成 任务 任务2 完成 任务 任务3 完成 所有结果: ['结果:任务1', '结果:任务2', '结果:任务3'] ``` 总耗时≈3 秒(最长任务的耗时),而非 1+2+3=6 秒。 **5. 避免 “阻塞陷阱”** 异步编程的核心是**必须使用异步版本的 IO 库**,否则会阻塞事件循环。 - 错误示例:在协程中使用同步 IO(如 \_time.sleep()\_、\_requests.get()\_),会阻塞整个事件循环: ```python import time async def bad_task(): print("开始") time.sleep(1) # 同步sleep,会阻塞事件循环! print("结束") # 即使创建两个任务,总耗时也是2秒(串行执行) async def main(): t1 = asyncio.create_task(bad_task()) t2 = asyncio.create_task(bad_task()) await t1 await t2 asyncio.run(main()) # 总耗时≈2秒(错误的并行) ``` 正确示例:使用异步 IO 库(如 \_asyncio.sleep\_ 替代 \_time.sleep\_,\_aiohttp\_ 替代 \_requests\_): ```python async def good_task(): print("开始") await asyncio.sleep(1) # 异步sleep,不阻塞事件循环 print("结束") async def main(): t1 = asyncio.create_task(good_task()) t2 = asyncio.create_task(good_task()) await t1 await t2 asyncio.run(main()) # 总耗时≈1秒(正确的并行) ``` ## 常用异步库 实际开发中,需使用异步版本的库处理 IO 操作: - 网络请求:\_aiohttp\_(替代 \_requests\_) - 数据库:\_aiomysql\_(MySQL)、\_asyncpg\_(PostgreSQL)、\_motor\_(MongoDB) - 文件 IO:\_aiofiles\_(替代内置 \_open\_) 例如:**用 \_aiohttp\_ 异步爬取网页**。 ```python import aiohttp import asyncio async def fetch_url(session, url): async with session.get(url) as response: # 异步HTTP请求 return await response.text() # 等待响应内容 async def main(): urls = [ "https://www.example.com", "https://www.python.org", "https://www.github.com" ] async with aiohttp.ClientSession() as session: # 异步会话 # 创建多个爬取任务 tasks = [fetch_url(session, url) for url in urls] results = await asyncio.gather(*tasks) # 并行爬取 print(f"爬取完成,共{len(results)}个页面") asyncio.run(main()) ``` ## 适用场景与局限性 ### 适用场景 - **IO 密集型任务**:网络请求(爬虫、API 调用)、文件读写、数据库操作等(等待时间远大于计算时间)。 - **高并发场景**:需要同时处理大量连接(如 WebSocket 服务、即时通讯应用)。 ### 局限性 - **不适合 CPU 密集型任务**:因 Python 的 GIL(全局解释器锁),单线程异步无法利用多核 CPU,复杂计算会阻塞事件循环(需配合多进程)。 - **学习成本高**:需要理解协程、事件循环等概念,调试难度高于同步编程。 - **库依赖**:必须使用异步版本的 IO 库,部分老旧库可能不支持异步。 ## 异步 vs 多线程 vs 多进程 | 技术 | 优势 | 劣势 | 适用场景 | | -------- | ---------------------------------- | -------------------------------- | ----------------------------------- | | 异步编程 | 轻量(百万级协程)、无线程切换开销 | 单线程,无法利用多核;依赖异步库 | IO 密集型、高并发(如网络服务) | | 多线程 | 可并发处理 IO 任务;共享内存方便 | 线程切换开销大;有线程安全问题 | 中等并发 IO 任务(如普通 Web 服务) | | 多进程 | 可利用多核 CPU;无 GIL 限制 | 内存占用高;进程通信复杂 | CPU 密集型任务(如数据分析、计算) |
毛林
2025年9月7日 12:01
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码