微信终端自研 C++协程框架的设计与实现

本文介绍了微信终端自研的C++协程框架'owl',该框架用于提供统一的编程模型,尤其适用于Android、iOS、macOS、Windows、Linux等平台。文章详细探讨了为何不直接使用C++20或现有开源方案,以及框架的设计思想,包括协程栈、调度器和通信机制。通过实例展示了从回调到协程的转变,以及结构化并发的优势。目前,owl已在多个微信终端产品中应用,未来计划进一步优化并开源。
摘要由CSDN通过智能技术生成

基于跨平台考虑,微信终端很多基础组件使用 C++ 编写,随着业务越来越复杂,传统异步编程模型已经无法满足业务需要。Modern C++ 虽然一直在改进,但一直没有统一编程模型,为了提升开发效率,改善代码质量,我们自研了一套 C++ 协程框架 owl,用于为所有基础组件提供统一的编程模型。

owl 协程框架目前主要应用于 C++ 跨平台微信客户端内核(Alita),Alita 的业务逻辑部分全部用协程实现,相比传统异步编程模型,至少减少了 50% 代码量。Alita 目前已经应用于儿童手表微信、Linux 车机微信、Android 车机微信等多个业务,其中 Linux 车机微信的所有 UI 逻辑也全部用协程实现

为什么要造轮子?

那么问题来了,既然 C++20 已经支持了协程,业界也有不少开源方案(如 libco、libgo 等),为什么不直接使用?

原因:

  • owl 基础库需要支持尽量多的操作系统和架构,操作系统包括:Android、iOS、macOS、Windows、Linux、RTOS;架构包括:x86、x86_64、arm、arm64、loongarch64,目前并没有任何一个方案能直接支持。
  • owl 协程自 2019 年初就推出了,而当时 C++20 还未成熟,实际上到目前为止 C++20 普及程度依然不高,公司内部和外部合作伙伴的编译器版本普遍较低,导致目前 owl 最多只能用到 C++14 的特性
  • 业界已有方案有不少缺点:
  1. 大多为后台开发设计,不适用终端开发场景
  2. 基本只支持 Linux 系统和 x86/x86_64 架构
  3. 封装层次较低,大多是玩具或 API 级别,并没有达到框架级别
  4. 在 C++ 终端开发没有看到大规模应用案例

Show me the code

那么协程比传统异步编程到底好在哪里?下面我们结合代码来展示一下协程的优势,同时也回顾一下异步编程模型的演化过程:

假设有一个异步方法 AsyncAddOne,用于将一个 int 值加 1,为了简单起见,这里直接开一个线程 sleep 100ms 后再回调新的值:

void AsyncAddOne(int value, std::function<void (int)> callback) {
    std::thread t([value, callback = std::move(callback)] {
        std::this_thread::sleep_for(100ms);
        callback(value + 1);
    });
    t.detach();
}

要调用 AsyncAddOne 将一个 int 值加 3,有三种主流写法:

1、Callback

传统回调方式,代码写起来是这样:

AsyncAddOne(100, [] (int result) {
    AsyncAddOne(result, [] (int result) {
        AsyncAddOne(result, [] (int result) {
            printf("result %d\n", result);
        });
    });
});

回调有一些众所周知的痛点,如回调地狱、信任问题、错误处理困难、生命周期管理困难等,在此不再赘述。

2、Promise

Promise 解决了 Callback 的痛点,使用 owl::promise 库的代码写起来是这样:

// 将回调风格的 AsyncAddOne 转成 Promise 风格
owl::promise AsyncAddOnePromise(int value) {
    return owl::make_promise([=] (auto d) {
        AsyncAddOne(value, [=] (int result) {
            d.resolve(result);
        });
    });
}

// Promise 方式
AsyncAddOnePromise(100)
.then([] (int result) {
    return AsyncAddOnePromise(result);
})
.then([] (int result) {
    return AsyncAddOnePromise(result);
})
.then([] (int result) {
    printf("result %d\n", result);
});

很显然,由于消除了回调地狱,代码漂亮多了。实际上 owl::promise 解决了 Callback 的所有痛点,通过使用模版元编程和类型擦除技术,甚至连语法都接近 JavaScript Promise。

但实践发现,Promise 只适合线性异步逻辑,复杂一点的异步逻辑用 Promise 写起来也很乱(如循环调用某个异步接口),因此我们废弃了 owl::promise,最终将方案转向了协程。

3、Coroutine

使用 owl 协程写起来是这样:

// 将回调风格的 AsyncAddOne 转成 Promise 风格
// 注:
// owl::promise 擦除了类型,owl::promise2 是类型安全版本
// owl 协程需要配合 owl::promise2 使用
owl::promise2<int> AsyncAddOnePromise2(int value) {
    return owl::make_promise2<int>([=] (auto d) {
        AsyncAddOne(value, [=] (int result) {
            d.resolve(result);
        });
    });
}

// Coroutine 方式
// 使用 co_launch 启动一个协程
// 在协程中即可使用 co_await 将异步调用转成同步方式
owl::co_launch([] {
    auto value = 100;
    for (auto i = 0; i < 3; i++) {
        value = co_await AsyncAddOne
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值