Coroutine 函数手册
依据源码:include/Coroutine.h、src/Coroutine.cpp。
本文按「符号 → 签名 → 作用 → 执行步骤 → 前置/后置条件 → 平台差异 → 边界与面试点」逐条说明。
0. 全局常量与线程局部变量
constexpr size_t kDefaultStackSize(Coroutine.cpp)
- 值:
128 * 1024(128 KiB) - 用途:协程栈大小(Linux
ucontext栈、CreateFiber栈参数)
线程局部(TLS)变量(Coroutine.cpp 定义,Coroutine.h 声明)
| 符号 | 类型 | 含义 |
|---|---|---|
tls_current_coro |
Coroutine* |
当前正在运行的协程;resume 入口设置,suspend 返回调度器后可能被清空 |
tls_current_scheduler |
Scheduler* |
当前 worker 线程关联的调度器实例 |
tls_current_worker |
SchedulerWorker* |
当前线程对应的 worker 结构(本地队列、抢占标记) |
tls_scheduler_ctx |
ucontext_t |
仅 Linux:调度器侧上下文,swapcontext 的另一端 |
tls_scheduler_fiber |
LPVOID |
仅 Windows:由 ConvertThreadToFiber 得到的“调度器 fiber” |
面试点:协程切换依赖 TLS;跨线程 resume 同一 Coroutine 在本实现中未做约束,设计上应在固定 worker 线程上驱动。
1. struct SchedulerWorker
定义位置:include/Coroutine.h
| 成员 | 含义 |
|---|---|
th |
该 worker 的 std::thread |
localQueue |
本 worker 优先消费的协程队列(deque<shared_ptr<Coroutine>>) |
localMtx |
保护 localQueue |
preemptRequested |
抢占标记;preemptLoop 周期性置 true,shouldPreempt 读并清零 |
2. enum class CoroutineState
| 值 | 含义 |
|---|---|
READY |
已创建,尚未首次 resume 跑完或刚创建 |
RUNNING |
resume 中,已进入协程体 |
SUSPEND |
suspend 后挂起,可再次 resume |
DONE |
协程函数执行完毕;resume 会直接返回 |
3. 类 Coroutine
3.1 explicit Coroutine(Func func)
- 签名:
Coroutine::Coroutine(Func func),Func = std::function<void()> - 文件:
src/Coroutine.cppL34–50 - 作用:构造协程对象并初始化平台相关的上下文与入口。
Linux(CBSF_USE_UCONTEXT)步骤:
state_ = READY- 分配
stack_,大小kDefaultStackSize getcontext(&ctx_),失败抛runtime_error("getcontext failed")- 设置
ctx_.uc_stack指向栈与大小,uc_link = nullptr makecontext(&ctx_, entry, 1, this),入口为静态entry
Windows(CBSF_USE_FIBER)步骤:
CreateFiber(kDefaultStackSize, fiberEntry, this)- 失败抛
runtime_error("CreateFiber failed")
异常:构造失败抛 std::runtime_error。
面试点:栈固定 128KB,深递归或大局部数组可能栈溢出。
3.2 ~Coroutine()
- 文件:
src/Coroutine.cppL52–56 - 作用:释放 Windows Fiber(
DeleteFiber)。 - Linux:析构中不释放
ucontext资源(栈由unique_ptr管理);无额外系统调用。
3.3 Coroutine(Coroutine&& other) noexcept
- 文件:
src/Coroutine.cppL58–73 - 作用:移动构造;转移
func_、状态与平台上下文/栈。 - 副作用:
other.state_ = DONE;Windows 上other.fiber_ = nullptr,避免双重DeleteFiber。
注意:移动后原对象处于“不可用/已完成”语义,避免再 resume。
3.4 void resume()
- 文件:
src/Coroutine.cppL75–90 - 作用:从当前调度器上下文切换到本协程上下文并开始/继续执行。
前置条件(隐含):
state_ != DONE,否则直接返回。tls_current_scheduler != nullptr,否则直接返回(无法切回调度器)。
步骤:
state_ = RUNNINGtls_current_coro = this- Linux:
swapcontext(&tls_scheduler_ctx, &ctx_) - Windows:
SwitchToFiber(fiber_) - 从
suspend返回后:若tls_current_coro == this,则置tls_current_coro = nullptr
面试点:
resume必须在已设置tls_current_scheduler的线程中调用(通常即run_on_worker_thread)。- Linux 路径依赖
tls_scheduler_ctx在首次swapcontext前已处于合法状态(见下文「实现注意」)。
3.5 void suspend()
- 文件:
src/Coroutine.cppL92–106 - 作用:从协程切回调度器上下文。
前置条件:tls_current_scheduler != nullptr,否则直接返回。
步骤:
- 若
state_ == RUNNING,则改为SUSPEND - Linux:
swapcontext(&ctx_, &tls_scheduler_ctx) - Windows:若
tls_scheduler_fiber非空,则SwitchToFiber(tls_scheduler_fiber)
面试点:DONE 后 entry 仍会调用 suspend(),此时 state_ 已是 DONE,suspend 不会把状态改回 SUSPEND,但仍会切回调度器 fiber。
3.6 CoroutineState state() const
- 文件:
include/Coroutine.hL65 - 作用:返回当前状态枚举。
3.7 static void Coroutine::entry(intptr_t arg)(Linux,私有)
- 文件:
src/Coroutine.cppL15–22 - 作用:
makecontext的 C 风格入口;arg为this指针。
步骤:
coro = reinterpret_cast<Coroutine*>(arg)- 若
coro->func_非空则调用 coro->state_ = DONEcoro->suspend()切回调度器
3.8 static void WINAPI Coroutine::fiberEntry(LPVOID lpParameter)(Windows,私有)
- 文件:
src/Coroutine.cppL24–31 - 作用:与
entry等价,Fiber 回调形式。
4. 类 Scheduler
4.1 static Scheduler& instance()
- 文件:
include/Coroutine.hL88–91 - 作用:Meyers 单例,进程内唯一
Scheduler。
4.2 template<typename F, typename... Args> void submit(F&& f, Args&&... args)
- 文件:
include/Coroutine.hL93–97 - 作用:将可调用对象打包为
std::function<void()>,封装进Coroutine,提交到submitTask。
实现细节:
std::bind(std::forward<F>(f), std::forward<Args>(args)...)submitTask(std::make_shared<Coroutine>(std::move(task)))
面试点:std::bind 对重载、引用包装等边界需注意;与 ThreadPool 头里“无参不用 bind”的注释是不同文件的选择。
4.3 template<typename F, typename... Args> void go(F&& f, Args&&... args)
- 文件:
include/Coroutine.hL99–102 - 作用:
submit的别名。
4.4 void submitTask(std::shared_ptr<Coroutine> coro)(私有)
- 文件:
src/Coroutine.cppL109–126 - 作用:入队一个协程任务并唤醒 worker。
步骤:
- 若
!running_.load(),调用start(1)隐式启动调度器 - 若当前线程 TLS:
tls_current_scheduler == this且tls_current_worker非空
→ 加锁tls_current_worker->localMtx,localQueue.emplace_back(move(coro)) - 否则
→ 加锁globalMtx_,globalQueue_.emplace_back(move(coro)) cv_.notify_one()
面试点:同调度器实例上,在 worker 内 submit 会优先进本地队列,有利于局部性。
4.5 std::shared_ptr<Coroutine> popLocalTask(SchedulerWorker& worker)(私有)
- 文件:
src/Coroutine.cppL128–134 - 作用:从指定 worker 的本地队列队首弹出一个协程;空则
nullptr。
4.6 std::shared_ptr<Coroutine> popGlobalTask()(私有)
- 文件:
src/Coroutine.cppL136–142 - 作用:从全局队列队首弹出;空则
nullptr。
4.7 std::shared_ptr<Coroutine> stealTask(SchedulerWorker& self)(私有)
- 文件:
src/Coroutine.cppL144–154 - 作用:工作窃取:遍历其他 worker,从其本地队列队尾偷一个任务。
面试点:队首本地、队尾窃取是常见策略,减少与本地 pop 的冲突。
4.8 void start(size_t workerCount = hardware_concurrency())
- 文件:
src/Coroutine.cppL156–180 - 作用:启动调度器:创建
workerCount个 worker 线程 + 1 个抢占线程。
步骤:
workerCount == 0时改为1running_.compare_exchange_strong(false, true),已为 true 则直接返回(幂等)workers_.clear(),预分配,逐个make_unique<SchedulerWorker>()- 每个 worker:
w->th = thread{ [this,w]{ tls_current_worker=w; run_on_worker_thread(); tls_current_worker=nullptr; } } preemptThread_ = thread{ [this]{ preemptLoop(); } }
异常:run_on_worker_thread 内 Windows 上 ConvertThreadToFiber 失败会抛异常(见 4.12)。
4.9 void stop()
- 文件:
src/Coroutine.cppL182–201 - 作用:停止调度器并回收线程。
步骤:
running_.compare_exchange_strong(true, false),已为 false 则直接返回cv_.notify_all()唤醒等待中的 workerjoinpreemptThread_- 对每个 worker
jointh - 加锁清空
globalQueue_ workers_.clear()
注意:析构 ~Scheduler() 会调用 stop(),确保进程退出时线程收敛。
4.10 void configureTimeSlice(std::chrono::milliseconds slice)
- 文件:
src/Coroutine.cppL203–206 - 作用:设置
preemptLoop的睡眠间隔;slice.count() <= 0则忽略。
4.11 bool shouldPreempt() const
- 文件:
src/Coroutine.cppL208–211 - 作用:若当前 TLS 属于本调度器且
tls_current_worker有效,则原子地读取并清除该 worker 的preemptRequested。
返回:是否需要让出(给 safe_point 用)。
4.12 void run_on_worker_thread()
- 文件:
src/Coroutine.cppL222–277 - 作用:worker 线程主循环:取任务、
resume、未结束则重新入本地队列。
步骤概要:
tls_current_scheduler = this- Windows:
tls_scheduler_fiber = ConvertThreadToFiber(nullptr),失败抛runtime_error("ConvertThreadToFiber failed") - 若
tls_current_worker == nullptr,则指向栈上fallbackWorker(兼容非标准调用路径) - 循环:
coro = popLocal → popGlobal → steal- 若无任务:
unique_lock(globalMtx_),sleepingWorkers_++,cv_.wait直到!running或全局队列非空,sleepingWorkers_--;若!running则break - 若有任务且状态为
READY或SUSPEND:调用coro->resume() - 若
coro->state() != DONE:锁本地队列push_back,notify_one
- 退出循环后:Windows
ConvertFiberToThread()清理 fiber - 清空
tls_current_coro、tls_current_scheduler;若用过fallbackWorker则清空tls_current_worker
面试点:
- 未完成协程重新入本地队列,而不是全局队列。
- 等待条件仅用
globalQueue_.empty(),本地队列有任务时不会通过cv唤醒(依赖正在运行的 worker 自己处理;窃取者依赖其他 worker 活跃)。
4.13 void preemptLoop()(私有)
- 文件:
src/Coroutine.cppL213–220 - 作用:独立线程循环:每
timeSlice_睡眠一次,给所有 worker 的preemptRequested置true。
面试点:这是协作式抢占提示,不是操作系统抢占;协程需 safe_point/yield 才真正让出。
5. 自由函数
5.1 inline void yield() / inline void yield_now()
- 文件:
include/Coroutine.hL131–137 - 作用:若
tls_current_coro非空,调用其suspend()。
区别:当前二者实现完全相同。
5.2 void safe_point()
- 文件:
src/Coroutine.cppL279–285 - 作用:若当前在调度器上且存在当前协程,并且
shouldPreempt()为真,则yield()。
5.3 void sleep_for(std::chrono::milliseconds dur)
- 文件:
src/Coroutine.cppL287–290 - 作用:
std::this_thread::sleep_for(dur)后调用safe_point()。
面试点:这会阻塞整个 OS 线程,不是“只挂起当前协程”;长睡眠会占用 worker。
6. 实现注意(Linux ucontext 与代码审阅)
在 resume/suspend 中使用 swapcontext(&tls_scheduler_ctx, &ctx_) 与 swapcontext(&ctx_, &tls_scheduler_ctx)。
标准用法通常需要在 worker 线程首次切入协程前,对 tls_scheduler_ctx 执行一次 getcontext,以便 swapcontext 有合法的“调度器保存点”。当前 run_on_worker_thread 的 Linux 分支未展示该初始化(与 Windows 显式 ConvertThreadToFiber 不对称)。
文档建议:若在 Linux 上实际跑通,需对照你本地是否另有补丁或编译宏;面试时可答:“ucontext 路径需要保证 scheduler 上下文已 getcontext 引导,否则首次切换行为未定义。”
7. 与 Server 的调用关系(便于串联记忆)
Server::run 中会:
Scheduler::instance().configureTimeSlice(...)Scheduler::instance().start(...)
HTTP 连接处理仍主要走 ThreadPool::handleClient;协程调度器在旁路运行,业务协程需自行 submit 使用。
下一模块若要同样粒度,可直接说模块名(例如 Server 或 Threadpool),我按本文件结构继续写 docs/modules/detail/<Module>_FUNCTIONS.md。