**C++20协程的使用与实践**

C++20 通过引入协程(Coroutines)提供了一种轻量级的异步编程模型。与传统的线程或回调机制相比,协程可以让代码保持同步风格,同时隐藏线程切换的开销。下面从基础概念、实现细节到实战案例,系统阐述如何在项目中使用协程。


1. 协程核心概念

关键词 作用 说明
co_await 暂停协程并等待 awaiter 让协程在 awaiter 完成前挂起,类似于 await
co_yield 暂停协程并返回值给调用方 用于生成器模式,产生一个值后挂起
co_return 结束协程并返回结果 类似于函数返回值,但可以在任何点返回
awaiter 需要满足 Awaitable 协议的对象 await_ready(), await_suspend(), await_resume() 三个成员函数

协程本质上是一个状态机。编译器会把含有 co_ 关键字的函数拆分成若干状态段,并生成一个 “promise” 对象来保存局部状态。


2. 协程与标准库的配合

C++20 标准库为协程提供了若干适配器:

  • std::future + std::promise:传统同步/异步接口
  • `std::generator `:产生值的生成器
  • `std::task `:返回值为 `T` 的异步任务(在一些实现中可用)
  • std::async 的协程版(std::async 依旧是同步)

下面给出一个自定义 generator 的实现,演示 co_yield 的使用。

#include <coroutine>
#include <iostream>

template<typename T>
struct generator {
    struct promise_type {
        T value_;
        std::suspend_always yield_value(T v) {
            value_ = v;
            return {};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        generator get_return_object() {
            return generator{ std::coroutine_handle <promise_type>::from_promise(*this) };
        }
        void unhandled_exception() { std::exit(1); }
        void return_void() {}
    };

    using handle_t = std::coroutine_handle <promise_type>;
    handle_t h_;
    explicit generator(handle_t h) : h_(h) {}
    ~generator() { if (h_) h_.destroy(); }
    generator(const generator&) = delete;
    generator(generator&& other) noexcept : h_(other.h_) { other.h_ = nullptr; }

    struct iterator {
        handle_t h_;
        bool done_ = false;
        iterator(handle_t h) : h_(h) { if (h_) h_.resume(); }
        iterator& operator++() {
            h_.resume();
            if (h_.done()) done_ = true;
            return *this;
        }
        T operator*() const { return h_.promise().value_; }
        bool operator!=(const iterator& other) const { return done_ != other.done_; }
    };

    iterator begin() { return iterator{h_}; }
    iterator end() { return iterator{nullptr}; }
};

generator <int> range(int start, int end) {
    for (int i = start; i <= end; ++i)
        co_yield i;
}

int main() {
    for (int x : range(1, 5))
        std::cout << x << ' ';   // 输出 1 2 3 4 5
}

3. 典型应用:异步网络请求

使用协程可以将网络请求写成同步风格。以下示例使用 co_await 与假设的 http_get awaiter。

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

struct http_response {
    std::string body;
};

struct http_get {
    std::string url;
    http_get(std::string u) : url(std::move(u)) {}
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) const {
        std::thread([h, url = url] {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            // 模拟网络延迟
            std::cout << "Fetched: " << url << '\n';
            h.resume();   // 任务完成后恢复协程
        }).detach();
    }
    http_response await_resume() const noexcept {
        return { "Response body from " + url };
    }
};

std::coroutine_handle<> async_main() {
    http_response resp = co_await http_get("https://example.com");
    std::cout << "Body: " << resp.body << '\n';
}

int main() {
    auto h = async_main();
    // 主线程可以做其他工作,协程在后台完成
    std::this_thread::sleep_for(std::chrono::seconds(1));
}

说明:上述 http_get 是一个简化示例;实际项目中可使用 Boost.Asio、cpprestsdk 等库提供的 awaiter。


4. 性能考虑

  • 协程调度:协程本身是无上下文切换的,只在 co_await 处挂起;真正的线程切换取决于 awaiter 的实现。
  • 堆分配:每个协程需要一个堆分配的 promise 对象,尽量复用或使用 std::pmr 来减少碎片。
  • 内联优化:如果协程体很短,编译器可能把其展开为内联代码,避免额外栈帧。

5. 进一步阅读

  • 《C++20 进阶》, 刘汝佳
  • 《Effective Modern C++》, Scott Meyers(第六章关注协程)
  • 官方文档: cppreference.com

结语
协程是 C++20 引入的最具革命性的特性之一,它把异步编程的“破碎”变成同步式代码的自然延伸。掌握了基本的 co_await / co_yield 用法后,你可以轻松将它们嵌入到网络、IO、游戏循环等多种场景,实现高效、可读性强的异步程序。祝你编码愉快!

发表评论