Skip to content

协程原理

协程是用户态的轻量级线程,由运行时调度而非操作系统内核。理解协程的实现原理,有助于更好地使用协程编写高并发程序。

协程 vs 线程

线程是内核调度的基本单位,有独立的栈空间(约 1-8MB)、寄存器状态。线程切换需要内核介入,保存和恢复寄存器、栈,切换开销大(约 1-10μs)。

协程是用户态调度的执行单元,有独立的栈空间(约 2-8KB)、寄存器状态。协程切换不需要内核介入,只需要保存和恢复寄存器,切换开销小(约 10-100ns)。

线程可以并行执行(多核),协程只能并发执行(单核)但可以挂起和恢复。协程更适合高并发 I/O 场景,线程更适合 CPU 密集场景。

有栈协程

有栈协程每个协程有独立的栈,可以在任意嵌套调用中挂起。协程切换时保存栈指针、寄存器、程序计数器,恢复时恢复这些状态。

Go goroutine

Go goroutine 是有栈协程,初始栈大小 2KB,按需扩容(最大 1GB)。Goroutine 切换只需要保存三个寄存器:SP(栈指针)、PC(程序计数器)、DX(上下文指针)。Go 运行时将 goroutine 调度到 M(内核线程)上执行。

Lua coroutine

Lua coroutine 也是有栈协程,通过 lua_yield 挂起,lua_resume 恢复。Lua coroutine 的栈大小固定,创建时分配。Lua coroutine 用于协作式多任务,每个任务是一个 coroutine。

无栈协程

无栈协程没有独立的栈,协程状态保存在闭包或状态机中。无栈协程只能在顶层挂起,不能在嵌套调用中挂起。

JavaScript async/await

async 函数返回 Promise,await 等待 Promise 完成。async/await 是 Generator 函数的语法糖,编译器将 async/await 转换成状态机。状态机的状态表示当前执行位置,Promise 完成后继续执行。

Python async/await

Python 3.5 引入 async/await,基于事件循环(asyncio)和 Future。async 函数返回 coroutine 对象,await 等待 Future 完成。asyncio 事件循环运行协程,协程挂起时切换到其他协程。

协程调度

协程调度器决定哪个协程在哪个线程上运行。调度策略影响协程的公平性、响应性和吞吐量。

公平调度

公平调度保证每个协程都有机会执行,避免某个协程饿死。实现方式:时间片轮转,每个协程执行一定时间后切换;优先级调度,高优先级协程优先执行,但低优先级协程也会定期执行。

抢占式调度

抢占式调度允许调度器强制挂起当前协程,切换到其他协程。实现方式:信号抢占(如 Python 的 signal.setitimer)、协作抢占(协程主动让出 CPU,如 Go 的函数调用、通道操作)。

Go 的抢占式调度:函数调用时检查是否需要抢占,如果需要则切换协程。Go 1.14 引入基于信号的抢占,强制挂起执行时间过长的协程。

工作窃取

工作窃取是分布式调度策略,每个线程维护一个本地队列,队列为空时从其他线程的队列窃取任务。Go 的 GMP 调度器采用工作窃取:P(逻辑处理器)维护本地队列,队列为空时从全局队列或其他 P 窃取 G。

协程通信

协程间通信需要特殊机制,因为协程不共享内存(或共享内存需要同步)。

Channel

Channel 是 Go 的协程间通信机制,基于 CSP(Communicating Sequential Processes)模型。Channel 是有类型的管道,可以发送和接收值。Channel 可以是有缓冲的或无缓冲的。无缓冲 Channel 是同步的,发送方和接收方必须同时准备好。

Future/Promise

Future 是对异步结果的封装,Promise 是 Future 的可写版本。Python 的 concurrent.futures.Future,JavaScript 的 Promise。Future 可以等待结果、添加回调、取消任务。

Select

Select 可以等待多个 Channel 操作,哪个先完成就处理哪个。Go 的 select 语句,Python 的 asyncio.select。Select 用于多路复用,等待多个事件。

协程的应用

Web 服务

协程可以处理大量并发连接,每个连接一个协程。Go 的 net/http,每个请求一个 goroutine。Python 的 FastAPI,每个请求一个协程。协程避免了回调地狱,代码像同步代码一样简单。

爬虫

协程可以并发爬取多个页面,每个页面一个协程。协程可以控制并发数量,避免过多连接被服务器拒绝。协程可以等待 I/O 完成后继续执行,不会阻塞其他协程。

数据处理

协程可以并发处理数据,每个数据块一个协程。协程可以流水线处理,一个协程读取数据,一个协程处理数据,一个协程写入数据。Channel 连接流水线的各个阶段。

协程是高并发编程的利器,结合了事件驱动的高并发和多线程的简单性。理解协程的实现原理,有助于更好地使用协程编写高性能程序。