Appearance
正式定义
协程是程序组件的一种,它允许执行被挂起,并在之后从挂起点恢复。它是比线程更轻量级的用户态并发单元,其调度由程序控制,而非操作系统内核。
关键词:
- 协作式:协程自愿、显式地让出执行权(
yield),而不是被系统强行中断。 - 用户态:创建、调度、切换都在用户程序中完成,不涉及操作系统内核,开销极低(通常只是函数调用的级别)。
- 可挂起与恢复:这是协程最核心的能力。它能在任意点暂停,把执行现场(局部变量、程序计数器等)保存下来,之后可以原样恢复。
核心特点(与线程对比)
| 特性 | 线程 | 协程 |
|---|---|---|
| 所属层面 | 操作系统内核提供和管理 | 用户级,由运行时库或语言本身管理 |
| 调度方式 | 抢占式。由系统内核的调度器决定何时切换,不可预测。 | 协作式。协程自己主动让出控制权,程序员可预测切换点。 |
| 开销 | 大。需要陷入内核,进行上下文切换(保存寄存器、内存映射等)。 | 极小。通常只是操作几个寄存器和栈帧,在用户态完成。 |
| 并发数量 | 有限。受限于内核资源,创建数千个线程会导致系统性能急剧下降。 | 极高。可轻松创建数万甚至数十万个协程(如Go语言)。 |
| 共享与同步 | 需要复杂的锁、信号量等机制来同步共享内存访问,容易出错。 | 通常单线程内运行,无需锁。通过通道(Channel)等高级抽象安全通信。 |
| 适用场景 | 计算密集型、多核CPU并行。 | I/O密集型、高并发服务(网络服务器、爬虫等)。 |
为什么协程如此有用?(解决什么问题?)
主要解决 “高并发” 和 “异步I/O” 的痛点。
传统阻塞I/O模型的问题: 一个线程处理一个网络连接,当等待数据时(比如从数据库读取),线程被操作系统挂起,白白占用内存和切换开销。连接数一多(如C10K问题),线程数暴涨,系统不堪重负。
使用协程的方案(以Go为例):
- 为每个连接创建一个协程。
- 当协程需要等待网络I/O时,它主动让出(
yield)执行权,告诉调度器:“我去等数据了,你去服务其他就绪的协程吧”。 - I/O事件就绪后(数据到了),调度器会恢复这个协程继续执行。
- 从程序员角度看,代码是顺序的、同步的(
data := socket.read()),但底层却是非阻塞、异步的。这极大简化了并发编程。
典型实现与语言支持
- Go语言:协程是其核心特性,称为 Goroutine。语言内置强大的调度器,使用关键字
go即可启动。是协程最成功的应用范例之一。 - Python:通过
asyncio库和async/await关键字支持。 - JavaScript:虽然本身是单线程事件循环,但
async/await语法糖提供了类似协程的同步编程体验来处理Promise。
总结
协程的本质是一个可以暂停和恢复执行的函数,是用户态的、协作式的超轻量级线程。 它的最大价值在于:用同步的代码风格,写出高性能的异步程序,完美契合现代高并发、高I/O密度的网络服务需求,同时避免了多线程编程的复杂性和高开销。