TypechoJoeTheme

至尊技术网

统计
登录
用户名
密码

C++手写协程实现原理与示例

2025-11-14
/
0 评论
/
1 阅读
/
正在检测是否收录...
11/14

什么是协程?

协程(Coroutine)是一种比线程更轻量的并发编程模型,它允许函数在执行过程中被挂起,并在之后从挂起点继续执行。与多线程不同,协程是协作式的——它们不会被操作系统强制调度,而是由程序员显式控制何时让出执行权。这种机制特别适合高并发IO密集型场景,如网络服务器、异步任务处理等。

虽然C++20引入了标准协程支持,但理解其底层实现原理对深入掌握并发编程至关重要。本文将带你从零开始,用C语言风格的C++实现一个极简的协程库,帮助你理解协程的核心机制。

协程的核心:上下文切换

协程的本质在于“保存和恢复执行上下文”。所谓上下文,就是程序运行时的状态,包括栈指针、指令指针、寄存器值等。要实现协程切换,关键在于如何捕获当前执行状态,并在将来某个时刻精确地恢复它。

在POSIX系统中,setjmplongjmp 提供了这样的能力。setjmp 用于保存当前执行环境到一个jmp_buf结构中,而longjmp则可以从该结构恢复执行环境,使程序跳转回setjmp调用点,并让setjmp返回指定的非零值。

这正是我们构建协程的基础工具。

极简协程库实现

下面是一个基于setjmp/longjmp的协程实现框架:

cpp

include

include

include

include <setjmp.h>

// 协程最大数量
const int MAXCOROUTINES = 16; // 每个协程栈大小(字节) const int STACKSIZE = 8192;

struct Coroutine {
char* stack; // 栈空间
jmp_buf context; // 上下文
void (*func)(); // 协程函数
bool finished; // 是否执行完毕
};

Coroutine coroutines[MAXCOROUTINES]; int current = 0; // 当前运行的协程ID jmpbuf main_context; // 主协程上下文

// 切换到指定协程
void switch_to(int cid) {
if (setjmp(coroutines[current].context) == 0) {
current = cid;
longjmp(coroutines[cid].context, 1);
}
}

// 协程入口函数包装
void coroutine_entry() {
coroutines[current].func();
coroutines[current].finished = true;

// 执行完后切回主协程
longjmp(main_context, 1);

}

// 创建协程
int createcoroutine(void (*func)()) { static int nextid = 0;
int id = next_id++;

Coroutine& co = coroutines[id];
co.func = func;
co.finished = false;
co.stack = new char[STACK_SIZE];

// 设置栈顶指针并初始化上下文
char* stack_top = co.stack + STACK_SIZE - sizeof(void*);

// 使用汇编或平台相关方式设置栈指针(简化版)
// 实际中需确保在新栈上调用setjmp
if (setjmp(co.context) == 0) {
    // 模拟跳转到新栈执行
    // 注意:此为教学简化,生产环境需更严谨的栈切换
    char fake;
    ptrdiff_t offset = &fake - stack_top;
    if (offset > 0 && offset < STACK_SIZE) {
        // 这里应通过汇编真正切换栈,此处略
        coroutine_entry();
    }
}

return id;

}

上述代码展示了协程的基本结构。每个协程拥有独立的栈空间和上下文缓冲区。创建时通过setjmp保存初始状态,当协程首次被调度时,会跳转到coroutine_entry执行用户函数。

调度与协作

协程之间的切换必须由程序员主动触发。我们可以定义一个yield()函数来实现让出执行权:

cpp void yield() { int next = (current + 1) % MAX_COROUTINES; while (coroutines[next].finished && next != current) { next = (next + 1) % MAX_COROUTINES; } if (!coroutines[next].finished) { switch_to(next); } }

主循环负责启动所有协程并进行调度:

cpp void run_all() { if (setjmp(main_context) == 0) { for (int i = 0; i < MAX_COROUTINES; ++i) { if (!coroutines[i].finished && coroutines[i].func) { switch_to(i); } } } }

使用示例

cpp
void task1() {
for (int i = 0; i < 3; ++i) {
std::cout << "Task 1 - Step " << i << std::endl;
yield();
}
}

void task2() {
for (int i = 0; i < 3; ++i) {
std::cout << "Task 2 - Step " << i << std::endl;
yield();
}
}

int main() {
createcoroutine(task1); createcoroutine(task2);
run_all();
return 0;
}

输出将是两个任务交替执行,展现出协作式多任务的效果。

总结与思考

这个简易协程库虽然功能有限,但它揭示了协程的本质:通过保存和恢复执行上下文实现函数间的非抢占式切换。真正的生产级协程库(如Boost.Context)会使用内联汇编精确控制ESP/RSP寄存器,确保跨平台稳定性和性能。

值得注意的是,这种手动管理栈的方式存在诸多限制,比如无法在协程中使用局部变量地址传递给外部,且调试困难。现代C++20协程通过编译器支持解决了这些问题,但理解底层机制依然是成为高级C++开发者的必经之路。

协程调度轻量级线程上下文切换C++协程协程库setjmp/longjmp
朗读
赞(0)
版权属于:

至尊技术网

本文链接:

https://www.zzwws.cn/archives/38540/(转载时请注明本文出处及文章链接)

评论 (0)

人生倒计时

今日已经过去小时
这周已经过去
本月已经过去
今年已经过去个月

最新回复

  1. 强强强
    2025-04-07
  2. jesse
    2025-01-16
  3. sowxkkxwwk
    2024-11-20
  4. zpzscldkea
    2024-11-20
  5. bruvoaaiju
    2024-11-14

标签云