Python 使用 Asyncio 进行异步 Python 编程
我们常常会遇到需要处理大量 I/O 操作的场景,比如网络请求、数据库查询、文件读写等。
传统的同步编程往往效率低下,因为每个任务都需要按顺序执行,这就导致了性能瓶颈,尤其是当某些操作需要等待很长时间时(比如等待网络响应或文件读取)。为了提升效率,异步编程应运而生,而 Python 的 asyncio 就是解决这一问题的利器。
同步编程 vs 异步编程
在我们开始深入了解 asyncio 之前,先来看看同步编程和异步编程的区别。同步编程是指任务按顺序一个接一个地执行,只有当前一个任务完成,后一个任务才会开始。举个例子,假设你在做三个任务:
打电话 吃饭 看电视
在同步编程中,你会先做一个任务,等完成了才做下一个任务。也就是说,如果打电话这件事需要等待很久(比如电话接通前你需要等待 5 分钟),那么你就得无所事事地等这 5 分钟才能开始吃饭,吃饭做完之后再去看电视。
而异步编程的思路是:你可以在等待某个操作完成的同时,去做其他事情。比如在上面的例子中,当你打电话时,如果接通前需要等待 5 分钟,你完全可以利用这 5 分钟时间去吃饭,吃完饭再去看电视,等电话接通时你就可以立即继续接电话,做完后再去做下一个任务。
为什么选择 Asyncio?
在 Python 中,asyncio 是处理异步编程的标准库,它通过引入“协程”概念,允许你编写非阻塞的代码,充分发挥 CPU 空闲时的能力,避免了传统阻塞 I/O 的性能瓶颈。
想象一下你在写一个爬虫,程序需要向多个网页发送请求,如果每个请求都要等它完成才能继续发送下一个,那速度简直慢得令人抓狂。使用 asyncio,你可以同时发送多个请求,而不用等待每个请求的响应。
Asyncio 基本概念
要理解 asyncio,首先需要了解两个基本概念:协程(Coroutine)和 事件循环(Event Loop)。
协程:Python 中的协程是通过 async def定义的函数,它在执行时会挂起并允许其他协程运行,直到它完成任务。事件循环:事件循环是一个不断运行的循环,用来调度协程的执行。当我们用 asyncio.run()启动协程时,Python 会创建一个事件循环并开始执行。
编写第一个 Asyncio 程序
好了,开始动手编写异步程序吧!以下是一个简单的例子,展示了如何使用 asyncio 来执行异步任务。
import asyncio
# 定义一个模拟的异步 I/O 操作
asyncdefdo_work(task_name, delay):
print(f"开始任务:{task_name}")
await asyncio.sleep(delay) # 模拟 I/O 延迟
print(f"完成任务:{task_name}")
# 主函数
asyncdefmain():
# 创建多个任务,异步执行
task1 = asyncio.create_task(do_work("任务1", 3))
task2 = asyncio.create_task(do_work("任务2", 2))
task3 = asyncio.create_task(do_work("任务3", 1))
# 等待所有任务完成
await task1
await task2
await task3
# 运行事件循环
asyncio.run(main())
解释:
async def do_work(task_name, delay):这是一个异步函数,它模拟执行任务时的延迟,使用await asyncio.sleep(delay)来模拟 I/O 操作的等待。asyncio.create_task():用来创建并调度协程任务。它会立即启动任务并返回一个任务对象。await task1:等待task1完成后,才会继续执行task2和task3,这样我们就能够并发执行多个任务。
输出:
开始任务:任务1
开始任务:任务2
开始任务:任务3
完成任务:任务3
完成任务:任务2
完成任务:任务1
注意,虽然我们用 await 等待每个任务完成,但任务 2 和任务 3 并没有等任务 1 完成,它们是并发执行的。
异步编程的优势
1. 提高程序性能
异步编程让程序可以在等待 I/O 操作时,不浪费 CPU 资源,而是执行其他任务。这样,我们可以有效提高程序的吞吐量。
2. 适用于 I/O 密集型任务
异步编程特别适用于需要进行大量 I/O 操作的任务,比如爬虫、网络请求、文件操作等。在这些任务中,I/O 操作往往会占据大部分时间,异步编程能充分利用这些空闲时间来执行其他任务。
3. 节省内存和资源
在异步编程中,任务是通过协程的方式执行的,不需要为每个任务创建一个新的线程,因此比线程池的开销要小。这样可以减少内存使用,避免资源浪费。
结合实际应用:爬虫的异步实现
来个更实际的例子,假设你要实现一个爬虫,爬取多个网页的数据。如果你用同步方式,每个网页请求都得等到上一个完成才行,效率自然就慢了。那如何利用 asyncio 提高效率呢?下面是一个爬虫的异步实现示例:
import asyncio
import aiohttp
asyncdeffetch_page(url):
asyncwith aiohttp.ClientSession() as session:
asyncwith session.get(url) as response:
returnawait response.text()
asyncdefmain():
urls = ["https://www.example.com", "https://www.example.org", "https://www.example.net"]
tasks = [fetch_page(url) for url in urls]
pages = await asyncio.gather(*tasks)
for page in pages:
print(f"抓取到页面,长度为:{len(page)}")
asyncio.run(main())
解释:
使用 aiohttp库来异步地获取网页内容。通过async with aiohttp.ClientSession()创建一个异步会话,并在其中通过session.get()异步地获取网页。asyncio.gather(*tasks):将多个异步任务并发执行,并等待所有任务完成。这个程序可以同时向多个网站发起请求,而无需等待每个请求的响应,极大提高了爬虫的效率。
面试题的最优回答
面试题:什么是异步编程,Python 中如何实现异步编程?
最优答案:
“异步编程是一种程序执行模型,能够让程序在执行某个任务时,不需要等待任务完成就可以执行其他任务,通常用于 I/O 密集型操作。在 Python 中,asyncio 是用来实现异步编程的标准库,它通过协程(async def)和事件循环(asyncio.run())来实现异步任务的调度。通过 asyncio.create_task(),我们可以并发执行多个协程,从而提高程序的性能,尤其在需要处理大量 I/O 操作时,异步编程能够充分利用等待时间,提高系统的响应速度和吞吐量。”
异步编程虽然学习曲线较陡,但掌握了它,可以让你在处理大量网络请求、数据库操作时,提升程序的执行效率,避免因为 I/O 阻塞而造成的性能瓶颈。
也可以看我写的这篇文章《DeepSeek满血复活,直接起飞!》来进行本地搭建。
对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》。