C++ 中的协程:异步编程的新方向

协程(coroutine)是 C++20 标准引入的一种轻量级异步机制,它通过 co_awaitco_yieldco_return 关键字,让函数能够在执行过程中暂停和恢复,而不需要手动管理线程或状态机。与传统的回调或 Promise 方式相比,协程写法更直观、易读,并且可以在编译期完成大部分检查,极大地提升开发效率。

1. 协程的基本语法

#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>

// 协程返回类型
struct Task {
    struct promise_type {
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }          // 初始不挂起
        std::suspend_always final_suspend() noexcept { return {}; }   // 结束时挂起
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

promise_type 用于控制协程的生命周期。initial_suspend 定义协程开始时是否挂起,final_suspend 定义结束时挂起。suspend_always 让协程在完成后挂起,允许外部代码通过 operator() 触发恢复。

2. 示例:异步计数

下面演示一个简单的异步计数器,模拟网络请求或 I/O 操作。

Task async_count(int n) {
    for (int i = 1; i <= n; ++i) {
        std::cout << "count: " << i << std::endl;
        // 模拟异步等待
        co_await std::suspend_always{};
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
}

调用方式:

int main() {
    auto t = async_count(5);
    for (int i = 0; i < 5; ++i) {
        t();  // 手动恢复协程
    }
    return 0;
}

运行结果:

count: 1
count: 2
count: 3
count: 4
count: 5

3. 结合 std::futurestd::async

C++20 允许协程返回 std::future,实现真正的异步任务。下面用 co_return 将结果包装进 std::future

#include <future>

std::future <int> async_add(int a, int b) {
    co_return a + b;   // 结果直接返回到 future
}

调用:

int main() {
    auto fut = async_add(3, 4);
    std::cout << "Result: " << fut.get() << std::endl;  // 阻塞等待结果
}

4. 处理异常

协程内部抛出的异常会被 promise_type::unhandled_exception 捕获。可以自定义处理逻辑:

struct promise_type {
    // ...
    void unhandled_exception() {
        try {
            std::rethrow_exception(std::current_exception());
        } catch (const std::exception& e) {
            std::cerr << "Coroutine exception: " << e.what() << std::endl;
        }
    }
};

5. 与第三方库协同使用

  • Boost.Asio:利用协程实现异步 I/O 处理,代码更接近同步写法。
  • cppcoro:提供更丰富的协程工具,如 generator, async_generator 等。

6. 性能与注意事项

  • 协程不需要额外线程,栈开销小;但若协程函数内部有大量栈变量,仍会占用线程栈。
  • 在高并发场景下,协程的调度器设计决定性能;使用现成的事件循环(如 asio::io_context)可简化实现。
  • 与旧标准代码兼容时,最好将协程封装在一个独立模块,逐步替换。

结语

C++ 协程让异步编程变得更加简洁、可维护。虽然学习曲线稍高,但随着编译器和标准库的完善,协程正逐步成为现代 C++ 开发不可或缺的工具。希望这篇文章能为你开启协程之旅提供参考。

发表评论