C++ 標準庫直到 C++20 才引入原生協程,且 Boost.Coroutine 接口較繁瑣。參考 Lua 協程的極簡風格,僅需四個核心接口即可覆蓋大多數場景:
create_coroutine(func, arg) — 創建協程,返回 IDresume_coroutine(id, arg) — 恢復/啟動協程,傳入參數,返回 yield 值yield(arg) — 掛起當前協程,傳回值給調用者is_alive(id) — 判斷協程是否仍可恢復POSIX 提供 ucontext.h 系列函數,可在用戶態保存/恢復 CPU 寄存器、棧指針、指令指針:
getcontext(&ctx) — 獲取當前上下文makecontext(&ctx, func, argc, ...) — 修改上下文指向新函數入口swapcontext(&ouctx, &inctx) — 原子切換:保存 ouctx,恢復 inctx一個最小演示:
#include#include using namespace std; static char stack[2048]; static ucontext_t ctx, ctx_main; void func() { cout << "enter func" << endl; swapcontext(&ctx, &ctx_main); // yield cout << "func resume from yield" << endl; } int main() { getcontext(&ctx); ctx.uc_stack.ss_sp = stack; ctx.uc_stack.ss_size = sizeof(stack); ctx.uc_link = &ctx_main; // func 結束後返回 main makecontext(&ctx, func, 0); cout << "main: before coroutine" << endl; swapcontext(&ctx_main, &ctx); // resume func cout << "main: back from first yield" << endl; swapcontext(&ctx_main, &ctx); // resume again cout << "main: back again" << endl; return 0; }
struct Coroutine {
ucontext_t ctx; // 上下文
char* stack; // 獨立棧空間
void (*func)(void*); // 入口函數
void* arg; // 用戶參數
uintptr_t yield_val; // 雙向傳值緩衝
enum { READY, RUNNING, SUSPENDED, FINISHED } status;
};
READY → getcontext、配置棧、makecontext 指向統一入口 SchedulerImpl::Schedule、swapcontext(main, &cor->ctx)SUSPENDED → 直接 swapcontext(main, &cor->ctx)SUSPENDED、swapcontext(&cor->ctx, &main) 切回主上下文,返回主調用者傳入的值。cor->func(cor->arg),執行完畢標記 FINISHED、running_ = -1。uintptr_t SchedulerImpl::ResumeCoroutine(int id, uintptr_t y) {
Coroutine* cor = id2routine_[id];
if (!cor || cor->status == RUNNING) return 0;
cor->yield_val = y;
switch (cor->status) {
case READY: {
getcontext(&cor->ctx);
cor->ctx.uc_stack.ss_sp = cor->stack;
cor->ctx.uc_stack.ss_size = stack_size_;
cor->ctx.uc_link = &main_ctx_;
running_ = id;
cor->status = RUNNING;
makecontext(&cor->ctx, (void(*)())Schedule, 1, this);
swapcontext(&main_ctx_, &cor->ctx);
break;
}
case SUSPENDED: {
running_ = id;
cor->status = RUNNING;
swapcontext(&main_ctx_, &cor->ctx);
break;
}
}
uintptr_t ret = cor->yield_val;
if (running_ == -1 && cor->status == FINISHED) DestroyCoroutine(id);
return ret;
}
uintptr_t SchedulerImpl::Yield(uintptr_t y) {
if (running_ < 0) return 0;
int cur = running_;
running_ = -1;
Coroutine* cor = id2routine_[cur];
cor->yield_val = y;
cor->status = SUSPENDED;
swapcontext(&cor->ctx, &main_ctx_);
return cor->yield_val; // 返回 resume 時傳入的值
}
#include "coroutine.h" #includeusing namespace std; CoroutineScheduler* sched = nullptr; void func1(void* arg) { uintptr_t ret; cout << "func1 start, arg=" << arg << ", yield 1" << endl; ret = sched->Yield((uintptr_t)"func1 yield 1"); cout << "func1 resume, got: " << (const char*)ret << endl; ret = sched->Yield((uintptr_t)"func1 yield 2"); cout << "func1 resume, got: " << (const char*)ret << ", exit" << endl; } void func2(void* arg) { cout << "func2 start, arg=" << arg << ", yield 1" << endl; const char* y = (const char*)sched->Yield((uintptr_t)"func2 yield 1"); cout << "func2 resume, got: " << y << ", exit" << endl; } int main() { sched = new CoroutineScheduler(); int f1 = sched->CreateCoroutine(func1, (void*)111); int f2 = sched->CreateCoroutine(func2, (void*)222); bool stop = false; while (!stop) { stop = true; if (sched->IsCoroutineAlive(f1)) { stop = false; const char* y = (const char*)sched->ResumeCoroutine(f1, (uintptr_t)"resume func1"); cout << "main: func1 yielded -> " << y << endl; } if (sched->IsCoroutineAlive(f2)) { stop = false; const char* y = (const char*)sched->ResumeCoroutine(f2, (uintptr_t)"resume func2"); cout << "main: func2 yielded -> " << y << endl; } } delete sched; return 0; }
func1 start, arg=111, yield 1 main: func1 yielded -> func1 yield 1 func2 start, arg=222, yield 1 main: func2 yielded -> func2 yield 1 func1 resume, got: resume func1 main: func1 yielded -> func1 yield 2 func2 resume, got: resume func2 main: func2 yielded -> func2 yield 1 func1 resume, got: resume func1 main: func1 yielded -> func1 yield 2
| 優點 | 缺點 |
|---|---|
| 代碼極簡(~200 行)、無第三方依賴 | 依賴 POSIX ucontext,Windows 原生不支持 |
| 語法接近 Lua,雙向傳值直觀 | 棧大小固定,深度遞歸可能溢出 |
| 適合教學、嵌入式、輕量任務調度 | 無法利用多核(單線程協作式) |
現代項目建議遷移至 C++20 原生協程(co_await/co_yield)或成熟庫(libco、boost::coroutine2、asio::awaitable),以獲得標準化、跨平台、編譯器優化支持。