问题: 什么是协程,它和线程、进程的核心区别是什么?

👇 点击展开/折叠 面试背诵模板(建议先背这个) 协程是用户态下的轻量级执行单元,由程序自身显式调度(协作式),不依赖操作系统内核,本质是在单线程内通过事件循环管理多个任务。核心区别:进程是资源分配单位,线程是CPU调度单位,协程是用户态调度单位;进程和线程由操作系统抢占式调度,协程由程序主动让出(await)实现协作式调度;切换开销:进程 > 线程 >> 协程(协程切换仅保存栈和寄存器,无系统调用);内存占用:进程最大(MB级),线程次之(数MB),协程最小(数KB);并行性:进程可多核并行,线程在Python中受GIL限制无法并行CPU任务,协程单线程内并发但非并行。
---

调度机制的底层差异

维度 进程 线程 协程
调度者 操作系统内核 操作系统内核 程序自身(事件循环)
调度方式 抢占式(时间片轮转) 抢占式(时间片轮转) 协作式(主动await让出)
切换时机 时间片耗尽/中断 时间片耗尽/中断 I/O等待或主动await
切换是否涉及系统调用 ✅ 是(陷入内核) ✅ 是(陷入内核) ❌ 否(纯用户态)

内存与切换开销对比

1
2
3
4
5
6
7
8
9
10
11
12
13
# 协程的极简切换示例(无锁、无系统调用)
import asyncio

async def task(name):
print(f"任务 {name} 开始")
await asyncio.sleep(1) # 主动让出,切换至其他协程
print(f"任务 {name} 结束")

async def main():
await asyncio.gather(task("A"), task("B"), task("C"))

asyncio.run(main())
# 三个任务在单线程内并发执行,总耗时约1秒
对比项 进程切换 线程切换 协程切换
保存上下文 页表、内存映射、寄存器、栈 寄存器、栈、PC 仅寄存器、栈(极简)
内核态切换 有(系统调用) 有(系统调用) 无
典型耗时 ~微秒级(1-10μs) ~微秒级(0.5-1μs) ~纳秒级(数十ns)
内存占用 ~MB级(独立空间) ~数MB(栈默认1-8MB) ~数KB(栈动态增长)

协程的核心特性与局限

特性(优势):

  • 极高并发:单线程可轻松管理数万个协程,而线程受系统资源限制(通常数千)。
  • 无锁编程:单线程内无需考虑竞态条件,天然避免死锁和数据竞争(前提是不混用多线程)。
  • 资源高效:创建和销毁开销极低,适合高并发I/O场景(如Web服务器、爬虫)。

局限(劣势):

  • 无法利用多核:协程本身在单线程内运行,不能并行计算。若需多核,需配合多进程(如ProcessPoolExecutor)。
  • 阻塞操作致命:协程内若执行同步阻塞I/O(如time.sleep、requests.get)会阻塞整个事件循环,必须使用异步版本(如asyncio.sleep、aiohttp)。
  • 侵入性强:所有依赖库必须支持异步(async/await),历史遗留同步代码难以迁移。

Python协程的发展简史

版本 实现方式 说明
Python 2.5 yield 生成器协程 基于生成器的半协程,功能有限
Python 3.4 @asyncio.coroutine + yield from asyncio 标准库引入,语法较繁琐
Python 3.5 async/await 语法 原生协程支持,取代生成器协程(推荐)
Python 3.7+ asyncio.run() 简化事件循环启动,统一API

三者的选型速查口诀(拓展补充)

计算密集用进程,I/O密集用线程,海量连接用协程。
协程是线程的“升级版”——在I/O密集型高并发场景下,协程能以极低资源支撑远超线程数量的并发连接,但需接受异步编程范式的改造。