ucontext在现代linux上失效因glibc 2.28+废弃getcontext/makecontext/swapcontext,栈对齐与信号上下文机制变更;需改用setjmp/longjmp或boost.context等替代方案。

ucontext在现代Linux上为什么跑不起来
因为getcontext/makecontext/swapcontext在glibc 2.28+默认被标记为废弃,编译会警告,运行时可能触发Illegal instruction——不是你代码写错了,是内核线程栈对齐和信号上下文保存机制变了。
- 实际可用的最低glibc版本是2.27(CentOS 8.5 / Ubuntu 18.04),再新就得换方案
-
makecontext要求栈必须手动分配且对齐到16字节,用malloc直接分配大概率崩,得用aligned_alloc(16, size) - 不能在
signalhandler里调用任何ucontext函数,否则栈帧错乱
fiber比ucontext更靠谱但得自己管栈
Windows的CreateFiber/SwitchToFiber稳定,Linux没原生fiber,得靠clone或setjmp/longjmp模拟。主流选择是boost.context或手写setjmp版——它不依赖glibc版本,但栈管理完全裸露给你。
- 每个fiber需独立栈空间,典型大小是64KB~256KB;太小容易溢出,太大浪费内存
-
setjmp保存寄存器状态时不保存浮点/SIMD寄存器,做数学计算密集型协程要额外处理 - 切换开销约30~50ns(x86-64),比
ucontext快一倍,但首次切换仍需sigaltstack设备用栈
调度器核心就三件事:挂起、唤醒、选下一个
别碰线程池或IO多路复用,轻量级协程调度器只负责“谁该跑”,不负责“怎么等”。所有阻塞操作(如read、sleep)必须包装成非阻塞+回调,或者用epoll托管后主动让出。
- 用
std::deque存就绪队列,std::vector存休眠队列(按超时时间排序) - 每次调度前检查休眠队列头是否到期,是则移入就绪队列
- 切出时调用
swapcontext或longjmp跳转到调度器入口;切入时从setjmp返回值判断是首次启动还是被唤醒 - 避免在协程里直接
delete自己,统一由调度器回收栈内存
最常踩的坑:栈溢出和跨协程变量生命周期
协程栈是独立分配的,但局部变量地址仍在原栈上——如果在协程A里取了&local_var,再切到B执行,那个地址早就失效了。
立即学习“C++免费学习笔记(深入)”;
- 所有传给协程的参数必须拷贝或堆分配,禁止传栈变量地址
- 协程函数不能是lambda捕获栈变量,必须是普通函数或static lambda(无捕获)
- 调试时用
valgrind --tool=helgrind能抓到栈指针误用,但asan对协程栈支持有限 - 不要用
std::thread_local,每个协程看不到彼此的thread_local变量
协程调度器真正难的不是切换,是让所有协作点都意识到“我随时可能被切走”——比如一个std::string拼接循环,中间没让出,就卡死整个调度器。








