Coroutine 协程
CHAPTER 04

Coroutine 协程

3 分钟阅读 · 655 字

对应文件:

  • 接口:include/Coroutine.h
  • 实现:src/Coroutine.cpp

1. 模块定位

该模块实现了跨平台协程与调度器:

  • Linux:ucontext
  • Windows:Fiber

目标是提供用户态协作式并发,并包含工作窃取与时间片抢占标记能力。

2. 核心对象

2.1 Coroutine

  • 状态:READY / RUNNING / SUSPEND / DONE
  • 核心接口:
    • resume()
    • suspend()
    • state()
  • 上下文资源:
    • Linux:ucontext_t + stack
    • Windows:LPVOID fiber_

2.2 Scheduler

  • 单例:Scheduler::instance()
  • 队列模型:
    • 全局队列 globalQueue_
    • 每个 worker 的本地队列 localQueue
  • 并发组件:
    • worker 线程集合 workers_
    • 抢占线程 preemptThread_
    • 条件变量 cv_

3. 线程局部上下文(TLS)

  • tls_current_coro
  • tls_current_scheduler
  • tls_current_worker
  • 平台补充:
    • Linux:tls_scheduler_ctx
    • Windows:tls_scheduler_fiber

它们用于在 resume/suspend/yield 期间定位当前执行上下文。

4. 关键执行流程

4.1 协程构造与入口

  • 构造函数分平台创建上下文
  • entry/fiberEntry 执行 func_
  • 执行结束后设置 DONE 并 suspend() 回到调度器

4.2 resume()

  • 仅对未 DONE 协程生效
  • 更新状态 RUNNING
  • 设置 tls_current_coro = this
  • 平台切换:
    • Linux:swapcontext(&tls_scheduler_ctx, &ctx_)
    • Windows:SwitchToFiber(fiber_)

4.3 suspend()

  • 仅在存在 tls_current_scheduler 时有效
  • RUNNING -> SUSPEND
  • 切回调度器上下文

4.4 Scheduler::run_on_worker_thread()

循环调度策略:

  1. 取本地任务
  2. 取全局任务
  3. 从其他 worker 窃取任务
  4. 无任务则等待 cv_

任务执行后:

  • 若协程未完成,重新放回本地队列
  • 若完成则释放共享指针生命周期

5. 抢占相关能力

  • preemptLoop() 每个时间片将 worker 的 preemptRequested=true
  • shouldPreempt() 读取并清除该标记
  • safe_point() 在安全点触发 yield()
  • sleep_for() 休眠后调用 safe_point()

注意:这是“协作式抢占标记”,不是内核级硬中断抢占。

6. 任务提交行为

  • submitTask() 若调度器未启动,会 start(1)
  • 若调用线程本身是该调度器 worker,则优先入本地队列
  • 否则入全局队列并 notify_one

7. 已知特点与风险

  • 优点:
    • 跨平台上下文切换封装完整
    • 本地队列 + 全局队列 + work stealing 结构清晰
    • 有时间片抢占信号机制
  • 风险:
    • 业务代码需要主动 safe_point/yield,否则会长时间占用 worker
    • 与网络 I/O 主链路尚未完全融合,当前 HTTP 主流程仍主要是线程池同步处理

8. 面试回答模板(简版)

“我们协程是用户态实现,Linux 用 ucontext,Windows 用 Fiber。调度器采用全局队列 + 本地队列 + work stealing。抢占不是强制中断,而是通过时间片标记和 safe_point 协作让出。当前协程基础设施已成熟,下一步是把 I/O 事件驱动与协程调度彻底打通。”

全部章节