← 返回题目列表

Python 中协程是什么?async 和 await 的作用是什么?

高频 简单 第 1 / 27 题 更新于 2026/07/27
协程asyncawaitasyncio

简化版

协程是可以在执行过程中主动暂停、稍后恢复的函数执行单元。async def 定义协程函数,调用后得到协程对象;await 用来等待另一个可等待对象完成,并把执行权交还给事件循环,让其他任务有机会运行。

详细版

基本写法:

import asyncio

async def fetch_data():
    await asyncio.sleep(1)
    return "done"

async def main():
    result = await fetch_data()
    print(result)

asyncio.run(main())

关键点:

  • async def 定义的是协程函数;
  • 调用协程函数不会立刻执行完整逻辑,而是返回协程对象;
  • await 只能出现在异步函数中;
  • await 等待 I/O、定时器、Task 等可等待对象;
  • 协程适合高并发 I/O,不适合直接加速 CPU 密集计算。

常见误区:

coro = fetch_data()  # 这里只是创建协程对象

如果不 await coro,也不把它包装成 Task 调度执行,它不会按你预期完成,还可能出现“coroutine was never awaited”警告。

面试回答可以抓住一句:协程通过在等待点主动让出执行权,用一个线程管理大量 I/O 等待任务。

完整版教学

一、协程解决的核心问题是什么

很多服务端程序的瓶颈不是 CPU 不够,而是大量时间在等:

  • 等数据库;
  • 等 Redis;
  • 等 HTTP 接口;
  • 等磁盘或网络 I/O;
  • 等定时器。

传统同步代码在等待期间会卡住当前执行流。多线程可以缓解,但线程多了会带来创建成本、上下文切换、锁竞争等问题。

协程的思路是:当一个任务等 I/O 时,主动暂停,把执行权让出去;事件循环继续调度其他可运行任务。这样一个线程也能管理大量并发连接。

二、async def 到底做了什么

普通函数:

def foo():
    return 1

result = foo()

调用后会立即执行函数体并返回结果。

协程函数:

async def foo():
    return 1

coro = foo()

调用后得到的是协程对象,不是最终结果。协程对象需要被事件循环驱动,通常通过:

await foo()

或者:

asyncio.create_task(foo())

才能真正参与调度。

三、await 的本质是等待并让出控制权

await 不是普通的阻塞等待。它等待一个可等待对象完成,同时把控制权交还给事件循环。

async def task():
    await asyncio.sleep(1)

这里的 asyncio.sleep(1) 不会阻塞线程 1 秒,而是告诉事件循环:“这个协程 1 秒后再继续,你先运行别人。”

这也是异步代码能提升 I/O 并发的原因。

四、协程和线程的区别

协程通常由事件循环在单线程内调度,切换点主要出现在 await。线程由操作系统调度,可能在你不容易预期的位置切换。

对比:

维度协程线程
调度者事件循环操作系统
切换点通常在 await抢占式,位置不完全可控
成本更轻更重
适合任务高并发 I/O阻塞 I/O、兼容同步库
CPU 密集不适合直接加速Python 线程也受 GIL 影响

五、协程不是让代码自动变快

如果异步函数里写阻塞代码:

async def bad():
    time.sleep(3)

事件循环会被卡住,其他协程也不能运行。异步编程要求整个调用链尽量使用非阻塞 API。

正确写法:

async def good():
    await asyncio.sleep(3)

如果必须调用同步阻塞函数,可以考虑 asyncio.to_thread() 或线程池。

六、常见误区与追问

概念含义面试重点
协程函数async def 定义的函数调用后返回协程对象
协程对象可被等待的执行单元不调度不会运行
await等待 awaitable 并让出控制权只能在异步上下文中使用
Task事件循环调度协程的包装支持并发推进、取消、结果获取
  • 误区:协程等于线程。 协程通常在事件循环线程内协作切换,线程由操作系统调度,两者成本和并发模型不同。
  • 误区:async/await 能让 CPU 计算自动变快。 它主要提升 I/O 等待场景的并发效率,CPU 密集任务仍会占住执行线程。
  • 误区:await 只是语法上的等待。 await 的关键是等待期间把控制权交还事件循环,让其他就绪任务继续执行。
  • 追问:为什么调用 async 函数不打印函数体里的日志? 因为只创建了协程对象,还没有被 await 或创建成任务调度。
  • 追问:什么对象能被 await? 协程对象、Task、Future,以及实现 awaitable 协议的对象。
  • 追问:协程适合什么业务场景? 高并发网络请求、异步数据库访问、消息消费、爬虫等大量时间花在等待 I/O 的场景。

记忆钩子:async def 造协程,await 让出控制权,事件循环负责在等待间隙推进其他任务。

七、加强记忆

协程的关键不是“函数前面加 async”,而是“在 await 点主动让出执行权”。async def 创建协程函数,调用得到协程对象;await 等待可等待对象并交还控制权。它适合高并发 I/O,不能自动加速 CPU 密集任务,也怕在协程里塞阻塞调用。