步骤 1:环境准备

  1. 克隆仓库

    git clone https://github.com/hnes/libaco.git
    cd libaco
    
  2. 安装依赖
    确保系统已安装:

    • GCC 编译器
    • GNU Make
    • Valgrind(可选,用于内存检测)
  3. 编译库和示例

    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); // 再次恢复协程
  • 执行流程
    1. 保存当前主协程上下文
    2. 若首次启动:
      • 初始化协程栈
      • 跳转至co_func
    3. 若非首次:
      • 从上次yield位置恢复
  • 必须在主协程上下文中调用
7. 资源释放
aco_destroy(co);          // 销毁协程
aco_share_stack_destroy(sstk); // 销毁共享栈
aco_destroy(main_co);     // 销毁主协程
  • 销毁顺序
    1. 先销毁所有非主协程
    2. 再销毁共享栈
    3. 最后销毁主协程
  • 安全检测:销毁后指针应置NULL

8. 协程主动让出

void co_func() {
    aco_yield();
}
  • 内部操作
    1. 保存当前协程的寄存器上下文
    2. 若使用共享栈:
      • 将当前栈内容拷贝到私有保存栈
    3. 恢复主协程上下文
  • 只能在非主协程中调用

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:编译选项

  1. 32位支持

    gcc -m32 -O2 acosw.S aco.c example.c -o example
    
  2. 共享 FPU 环境(提升性能)

    gcc -DACO_CONFIG_SHARE_FPU_MXCSR_ENV -O2 acosw.S aco.c example.c -o example
    
  3. Valgrind 支持

    gcc -DACO_USE_VALGRIND -O2 acosw.S aco.c example.c -o example
    valgrind --leak-check=full ./example
    

步骤 5:最佳实践

  1. 栈类型选择

    • 独立栈:协程生命周期长,需保留完整上下文。
    • 共享栈:大量短生命周期协程,节省内存(需注意栈覆盖问题)。
  2. 性能优化

    • 预分配足够大的 save_stack_sz 避免运行时扩展。
    • 使用 ACO_CONFIG_SHARE_FPU_MXCSR_ENV 减少上下文切换开销。
  3. 错误处理

    • 确保子协程通过 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;
}

细节请看官方文档

Logo

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

更多推荐