小白学 Python
课程GitHub
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录GitHub
Python 环境加载中…
async / await0 / 4
1234

async / await

4 道练习题·预计 35 分钟·做对一题解锁下一段

教学 01 / 05· 已读

async/await:让一个线程同时等很多件事

各位写过爬虫吗?随手一段:

python
import time

def fake_get(url):
    time.sleep(0.01)
    return f'GOT {url}'

urls = [f'https://example.com/{i}' for i in range(5)]
results = [fake_get(u) for u in urls]
print(f'拿到 {len(results)} 个')

每个请求假装耗时 0.01 秒,5 个串起来就是 0.05 秒。如果是真实网络请求,每个 200 毫秒,100 个串起来就是 20 秒——慢得想骂人。

「那好办,开线程嘛!」有人立刻反应过来,「100 个 URL 就开 100 个线程,一起跑。」

线程当然能解决问题。但开 100 个线程是不是有点奢侈?这 100 个线程 99% 的时间都在干同一件事:等网络包回来。本质上这是个等待问题,不是个计算问题。为了「等」而开 100 个操作系统级的线程,CPU 不开心,内存也不开心。

而且线程一多,「锁、竞态、死锁」这些老朋友就会接连找上门。你只是想抓个网页,怎么忽然要研究操作系统了?

另一条路:asyncio

Python 给了我们另一条路:asyncio。它的卖点很直白——

一个线程,同时等一百件事。

各位听到这句话的第一反应大概是:「这不科学吧,单线程怎么同时做一百件事?」

魔法的关键就一句话:

当某个协程在等 IO 的时候,让出 CPU 给其他协程;等回来再继续。

asyncio 没有偷偷开线程,也没有把 CPU 加速。它做的事很朴素——「等」可以重叠。三件事每件等 1 秒,串行要 3 秒;让它们的「等」重叠起来,1 秒就够了。

这一节的目标

  • 看懂 async def、await、asyncio.run() 这三个最基本的概念
  • 用 asyncio.gather 同时跑多个协程
  • 知道什么时候该用 async(IO bound),什么时候别用(CPU bound)

注意:浏览器里的 Python 沙盒不能联网,所以咱们用 asyncio.sleep 来模拟「等」的过程。逻辑和真实网络请求是一样的。

教学 02 / 05

一、协程函数:和普通函数差在哪

普通函数长这样:

python
def hello():
    return '你好'

print(hello())   # 你好

加一个 async 关键字,它就成了协程函数(coroutine function):

python
async def hello():
    return '你好'

print(hello())

输出大致是这样:

<coroutine object hello at 0x...>
RuntimeWarning: coroutine 'hello' was never awaited

各位看出区别了吗——同样写 print(hello()),普通函数返回的是字符串,协程函数返回的是一个 协程对象,而且还附赠一句警告:「这协程从来没被 await 过」。

关键认知

async def 定义的不是一个「会立刻跑的函数」——它返回一张「待执行的任务单」。

打个比方:

函数类型f() 的行为
普通函数立刻派人去执行,立刻拿到结果
协程函数写一张任务单递给你;什么时候做、谁做,另说

这张「任务单」就是协程对象。它必须被 await 或者扔给事件循环,里面的代码才会真正跑起来。

await:让协程跑起来

那怎么让任务单真的执行?用 await:

python
import asyncio

async def hello():
    return '你好'

async def main():
    result = await hello()    # ← 这里让 hello 真正跑
    print(result)

asyncio.run(main())

输出:

你好

await hello() 的语义是「请帮我把这张任务单完成,然后把结果给我」。

但**await 只能写在 async def 函数体内部**。在普通函数里写 await,Python 会报 SyntaxError: 'await' outside async function。

asyncio.run():进入异步世界的大门

「那我想在普通脚本里调一个 async def 怎么办?」答案就是 asyncio.run():

python
import asyncio

async def main():
    print('我在异步世界里')
    print('我还在异步世界里')

asyncio.run(main())

输出:

我在异步世界里
我还在异步世界里

asyncio.run() 是同步代码和异步代码之间的「门」:

  • 接收一个协程对象
  • 启动一个事件循环(event loop)
  • 把协程跑完,再关掉事件循环

记住一条:整个程序里通常只调一次 asyncio.run()——它就是从同步世界踏进异步世界的入口。

三件套:async def + await + asyncio.run

到这里,最基础的三件套你已经齐了:

工具作用
async def f()定义协程函数(写出一张任务单的能力)
await x等一张任务单跑完,拿结果
asyncio.run(c)从同步世界进入异步世界,跑一个协程

下一题就用这三个东西写一个最小的 hello world。

练习 1 / 4·第一个 async 函数题目有问题?

请用 async def 定义一个协程函数 hello,函数体打印 'hi'。

然后用 asyncio.run(hello()) 启动它。

输出应该是:

hi
main.py
可编辑
🔒做对当前题解锁下一段 ·0/4
本章目录

async / await

  1. 教学 01async/await:让一个线程同时等很多件事
  2. 教学 02一、协程函数:和普通函数差在哪
  3. 练习 1第一个 async 函数
  4. 教学 03二、`asyncio.sleep`:模拟「等」
  5. 练习 2 🔒串行 await 的顺序
  6. 教学 04三、`asyncio.gather`:一次等多个
  7. 练习 3 🔒用 gather 并发跑两个协程
  8. 练习 4 🔒并发抓取(模拟)
  9. 教学 05四、什么时候用 async,什么时候别用
← 上一章21 · 上下文管理器(with)
async / await
下一章 →23 · pyproject.toml + uv