如何在 C++20 中实现协程(Coroutine)?

C++20 引入了协程(Coroutine)的原语,使得在函数内部可以“挂起”(suspend)和“恢复”(resume)执行,而不需要手动管理状态机。下面给出一个完整的示例,展示如何实现一个简单的异步数据流,并用协程来生成和消费数据。

1. 基础概念

  • Generator:一个可迭代的对象,内部使用 co_yield 生成值。
  • Task:一个可以异步执行的函数,使用 co_return 返回结果。
  • Awaitable:任何可被 co_await 的对象,例如 std::futurestd::promise、自定义等待器。

2. 一个简易的 Generator 实现

#include <coroutine>
#include <exception>
#include <iostream>
#include <vector>

template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        std::exception_ptr exception;

        Generator get_return_object() {
            return Generator{
                .handle = std::coroutine_handle <promise_type>::from_promise(*this)
            };
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }

        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }

        void unhandled_exception() { exception = std::current_exception(); }
        void return_void() {}
    };

    std::coroutine_handle <promise_type> handle;

    ~Generator() { if (handle) handle.destroy(); }

    bool move_next() {
        if (!handle.done()) {
            handle.resume();
            return !handle.done();
        }
        return false;
    }

    T current() { return handle.promise().current_value; }
};

Generator <int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i; // 生成值
    }
}

说明

  • promise_type 保存当前值、异常等。
  • initial_suspend 让协程立即开始。
  • final_suspend 让协程在完成后暂停,等待外部销毁。
  • yield_value 存储当前值,然后暂停。

3. 使用 Generator

int main() {
    auto gen = range(1, 6); // 生成 1..5
    while (gen.move_next()) {
        std::cout << "Value: " << gen.current() << '\n';
    }
}

输出:

Value: 1
Value: 2
Value: 3
Value: 4
Value: 5

4. 一个简易的 Task(异步函数)

#include <coroutine>
#include <future>
#include <chrono>

struct Task {
    struct promise_type {
        std::promise <void> promise;
        Task get_return_object() {
            return Task{ .handle = std::coroutine_handle <promise_type>::from_promise(*this) };
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept {
            promise.set_value();
            return {};
        }
        void unhandled_exception() { promise.set_exception(std::current_exception()); }
        void return_void() {}
    };

    std::coroutine_handle <promise_type> handle;
    std::future <void> get_future() { return handle.promise().promise.get_future(); }
};

Task async_print(int n, std::chrono::milliseconds delay) {
    std::cout << "Start: " << n << '\n';
    co_await std::suspend_always{}; // 这里可以放置真正的异步等待
    std::this_thread::sleep_for(delay);
    std::cout << "End: " << n << '\n';
}

说明

  • 这里的 async_print 通过 co_await 暂停,模拟异步操作。实际项目中可替换为真正的异步等待器(如 std::futurestd::experimental::awaitable 等)。

5. 结合 Generator 与 Task

下面的例子展示如何将异步任务与生成器组合,产生一个“异步流”。

Generator<std::future<int>> async_numbers(int count) {
    for (int i = 0; i < count; ++i) {
        std::promise <int> prom;
        auto fut = prom.get_future();
        std::thread([i, prom = std::move(prom)]() mutable {
            std::this_thread::sleep_for(std::chrono::milliseconds(100 * i));
            prom.set_value(i * i); // 返回平方值
        }).detach();
        co_yield fut; // 生成一个 future
    }
}

消费:

int main() {
    auto gen = async_numbers(5);
    while (gen.move_next()) {
        auto fut = gen.current();
        std::cout << "Result: " << fut.get() << '\n';
    }
}

输出(大约每 100ms 一行):

Result: 0
Result: 1
Result: 4
Result: 9
Result: 16

6. 小结

  • 协程让我们能够以同步的写法描述异步逻辑,代码更简洁易读。
  • GeneratorTask 是两种常见的协程用例,分别对应生成器和异步任务。
  • 在实际项目中,建议使用标准库 std::experimental::generator(C++23)或第三方库(如 cppcoro)来避免手写 Promise 细节。

掌握协程后,你可以实现更高效的异步 I/O、事件驱动模型以及基于协程的轻量级线程池。祝你编码愉快!

发表评论