Python技术迷

如何使用 asyncio 实现异步编程?

讲真,Python 的 asyncio 啥时候成了面试必考题我是真没想到。前几年还在写多线程、multiprocessing,结果现在一开口就是“你会协程吗”“async/await熟不熟”。

但说归说,asyncio 确实是个香饽饽,不管你愿不愿意,干过 Web、爬虫、网络服务的 Python 程序员,早晚得跟它扯上关系。

先给大家举个栗子,啥叫“同步”:

import time

deffetch_data():
    time.sleep(2)
return"数据来了"

defmain():
    print("开始拉数据...")
    result = fetch_data()
    print(result)
    print("拉完了")

main()

这段代码看着简单,问题也很明显:fetch_data() 卡了两秒,整个进程都跟着等着,就这……面试官看到直接打分-10。

那你说,怎么用 asyncio 改写?核心就俩字:“异步”。

import asyncio

asyncdeffetch_data():
await asyncio.sleep(2)
return"数据来了"

asyncdefmain():
    print("开始拉数据...")
    result = await fetch_data()
    print(result)
    print("拉完了")

asyncio.run(main())

看到没?用了 async def 声明协程,用 await 让出控制权,再用 asyncio.run 启动事件循环,整个链路清清楚楚。

问题是,很多刚接触 asyncio 的兄弟一开始容易走弯路。最大误区:以为写了 async/await 就是并发了,实际根本不是。

来,我再上个经典坑示例👇:

import asyncio
import time

asyncdeffoo():
    print("foo开始")
await asyncio.sleep(2)
    print("foo结束")

asyncdefbar():
    print("bar开始")
await asyncio.sleep(3)
    print("bar结束")

asyncdefmain():
    start = time.time()
await foo()
await bar()
    print(f"耗时: {time.time() - start}")

asyncio.run(main())

你以为 foo 和 bar 会并发跑?天真了兄弟,这跟同步写法没啥两样。因为你是顺序地 await foo() -> await bar(),它们根本没“同时跑”。

正确姿势是这样写:

asyncdefmain():
    start = time.time()
    tasks = [foo(), bar()]
await asyncio.gather(*tasks)
    print(f"耗时: {time.time() - start}")

这才是真·并发跑协程,一个 gather 相当于组团上战场,兵分多路,效率嗷嗷高!

当然 asyncio 的坑远不止这些,还有一些“反人类”的地方。比如:

  1. 阻塞函数千万别写进去:time.sleep() 就是罪魁祸首,要用 asyncio.sleep()。
  2. **非协程函数不能随便 await**:比如你想 await requests.get(),抱歉,不支持,得用 aiohttp。
  3. 协程函数调用不等于执行:foo() 返回的是 coroutine object,不是你想象的结果,要用 await 或放进 asyncio.create_task() 才能动起来。

扯远了,说点实际场景。

当年我做一个微信公众号文章爬虫,要同时访问几十个公众号页面,那会儿用 requests + 多线程,结果容易被微信封 IP,后来换成 aiohttp + asyncio,一次性开 50 个协程抓页面,速度飞快,还能加限速保护,不容易被封。

代码大概长这样:

import aiohttp
import asyncio

asyncdeffetch(session, url):
asyncwith session.get(url) as response:
returnawait response.text()

asyncdefmain():
    urls = ['http://example.com/page1', 'http://example.com/page2', ...]
asyncwith aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
for html in results:
            print(len(html))

asyncio.run(main())

这段代码的精髓在于 aiohttp 的 ClientSession 是异步的,async with 保证了连接池自动管理,性能和资源都利用到位。

很多人问我,“你觉得 asyncio 能替代多线程吗?”我说不能。你要做 CPU 密集型任务,asyncio 真帮不上你,它主要是搞 I/O 密集的,比如网络请求、文件读写、数据库操作这些。

如果你真的想在 asyncio 下并发执行 CPU 任务,那你得用 run_in_executor:

import asyncio
import time

defblocking_task():
    time.sleep(3)
return"我卡主线程了"

asyncdefmain():
    loop = asyncio.get_running_loop()
    result = await loop.run_in_executor(None, blocking_task)
    print(result)

asyncio.run(main())

这就相当于扔给线程池去处理,主线程不受影响,算是官方给 asyncio 打的补丁。

总之吧,asyncio 是个好东西,但不能迷信。搞懂它的调度模型、任务机制、event loop 是关键,别一看到 async/await 就觉得自己写的是高级代码。面试要过,项目要跑得稳,脚踏实地是王道。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
🔥虎哥私藏精品 热门推荐🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,全部免费领取