C语言协程开源库libaco的使用
协程
·
步骤 1:环境准备
-
克隆仓库
git clone https://github.com/hnes/libaco.git cd libaco
-
安装依赖
确保系统已安装:- GCC 编译器
- GNU Make
- Valgrind(可选,用于内存检测)
-
编译库和示例
mkdir output bash make.sh
生成的可执行文件在
output
目录。
步骤 2:基本使用流程
1. 包含头文件
#include "aco.h" // 核心头文件
#include "aco_assert_override.h" // 可选,覆盖 assert 宏
2. 初始化协程环境
int main() {
aco_thread_init(NULL); // 初始化当前线程的协程环境
// ...
}
- 作用:初始化当前线程的libaco环境
- 必须第一个调用,且每个线程只需调用一次
- 参数为自定义错误处理函数指针(NULL使用默认)
- 记录当前FPU和MXCSR控制字状态
3. 创建主协程
aco_t* main_co = aco_create(NULL, NULL, 0, NULL, NULL);
- 主协程特征:
- 独占线程默认栈
- 必须第一个创建
- 作为所有协程的根节点
- 参数说明:
- 前两个NULL表示创建主协程
- 后两个NULL无意义(主协程无入口函数)
4. 创建子协程
aco_share_stack_t* sstk = aco_share_stack_new(0); // 创建共享栈
int arg = 42;
aco_t* co = aco_create(main_co, sstk, 0, co_func, &arg);
main_co
: 主协程指针sstk
: 共享栈,子协程共用0
: 初始保存栈大小(字节,0=默认64B)co_func
: 协程入口函数(void (*)(void)
)&arg
:用户自定义参数指针
- 共享栈特性:
- 可被多个协程复用(非同时)
- 默认大小2MB(0表示默认)
- 自动内存管理
- 带保护页的版本共享栈:
aco_share_stack_new2(size, 1) // 启用保护页检测栈溢出
5. 编写协程函数
void co_func() {
int* arg = (int*)aco_get_arg(); // 获取参数
printf("协程开始,参数: %d\n", *arg);
// 切换回主协程
aco_yield();
printf("协程恢复\n");
aco_exit(); // 必须调用以正确退出
}
6. 启动/恢复协程
在主函数中控制协程执行:
aco_resume(co); // 启动/恢复协程
// 协程执行到 aco_yield() 后会返回此处
aco_resume(co); // 再次恢复协程
- 执行流程:
- 保存当前主协程上下文
- 若首次启动:
- 初始化协程栈
- 跳转至
co_func
- 若非首次:
- 从上次yield位置恢复
- 必须在主协程上下文中调用
7. 资源释放
aco_destroy(co); // 销毁协程
aco_share_stack_destroy(sstk); // 销毁共享栈
aco_destroy(main_co); // 销毁主协程
- 销毁顺序:
- 先销毁所有非主协程
- 再销毁共享栈
- 最后销毁主协程
- 安全检测:销毁后指针应置NULL
8. 协程主动让出
void co_func() {
aco_yield();
}
- 内部操作:
- 保存当前协程的寄存器上下文
- 若使用共享栈:
- 将当前栈内容拷贝到私有保存栈
- 恢复主协程上下文
- 只能在非主协程中调用
9. 协程退出
void co_func() {
aco_exit(); // 正确退出方式
}
- 必须使用而非直接return
- 操作:
- 标记协程为结束状态(
co->is_end = 1
) - 自动切换回主协程
- 后续
aco_resume
将不再生效
- 标记协程为结束状态(
步骤 3:关键 API 详解
核心函数
-
aco_thread_init(aco_cofuncp_t last_word)
初始化线程级协程环境。last_word
可指定协程非法退出时的回调。 -
aco_create()
创建协程:- 主协程:参数全
NULL
- 子协程:需指定主协程、共享栈、入口函数及参数
- 主协程:参数全
-
aco_resume(aco_t* co)
从当前协程切换到co
。调用者必须是主协程。 -
aco_yield()
子协程主动让出执行权,切换回主协程。 -
aco_exit()
子协程退出,必须调用以避免断言失败。
共享栈管理
-
aco_share_stack_new(size_t sz)
创建共享栈,sz=0
使用默认大小(2MB)。 -
aco_share_stack_destroy()
销毁共享栈,确保无协程在使用。
步骤 4:编译选项
-
32位支持
gcc -m32 -O2 acosw.S aco.c example.c -o example
-
共享 FPU 环境(提升性能)
gcc -DACO_CONFIG_SHARE_FPU_MXCSR_ENV -O2 acosw.S aco.c example.c -o example
-
Valgrind 支持
gcc -DACO_USE_VALGRIND -O2 acosw.S aco.c example.c -o example valgrind --leak-check=full ./example
步骤 5:最佳实践
-
栈类型选择
- 独立栈:协程生命周期长,需保留完整上下文。
- 共享栈:大量短生命周期协程,节省内存(需注意栈覆盖问题)。
-
性能优化
- 预分配足够大的
save_stack_sz
避免运行时扩展。 - 使用
ACO_CONFIG_SHARE_FPU_MXCSR_ENV
减少上下文切换开销。
- 预分配足够大的
-
错误处理
- 确保子协程通过
aco_exit()
退出。 - 使用 Valgrind 检测内存问题。
- 确保子协程通过
示例代码
#include "aco.h"
#include <stdio.h>
#include "aco_assert_override.h"
void co_function() {
printf("协程运行\n");
aco_yield();
printf("协程再次运行\n");
aco_exit();
}
int main() {
aco_thread_init(NULL);
aco_t* main_co = aco_create(NULL, NULL, 0, NULL, NULL);
aco_share_stack_t* sstk = aco_share_stack_new(0);
aco_t* co = aco_create(main_co, sstk, 0, co_function, NULL);
printf("主协程启动子协程\n");
aco_resume(co);
printf("主协程恢复\n");
aco_resume(co);
aco_destroy(co);
aco_share_stack_destroy(sstk);
aco_destroy(main_co);
return 0;
}
细节请看官方文档

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)