在C++中实现协程的简易演示

在现代 C++(C++20 及以后)中,协程(Coroutines)提供了一种高效的异步编程方式。相比传统的回调或线程,协程可以让代码保持同步写法,同时隐藏底层的状态机细节。下面通过一个小例子,演示如何使用标准库实现一个简单的协程生成器,并结合 std::generator(在 C++20 标准库中并不存在,但我们可以用 cppcoro 或自行实现)来模拟。

1. 环境准备

  • 编译器:支持 C++20 的编译器,例如 GCC 11+、Clang 13+、MSVC 16.9+。
  • 标准库:使用 std::experimental::generator(在 libstdc++ 中)或自行实现一个基本的 generator。

为了简化代码,这里使用 std::experimental::generator(在 GCC 中可通过 -std=c++20 -lstdc++fs 编译),但如果没有支持,可以参考下面的自定义实现。

2. 基础协程生成器

#include <iostream>
#include <experimental/generator>

namespace stdex = std::experimental;

// 一个简单的协程生成器,产生 1~n 的整数
stdex::generator <int> range(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i; // 暂停并返回值
    }
}
  • co_yield 是协程关键字,类似 yield,用于返回一个值并挂起协程。
  • co_return 可用于在协程结束时返回一个值或结束状态。

3. 使用协程生成器

int main() {
    for (int val : range(10)) {
        std::cout << val << ' ';
    }
    std::cout << '\n';
}

编译运行后,输出:

1 2 3 4 5 6 7 8 9 10 

这段代码展示了如何在 for 循环中直接使用协程生成器,代码像同步一样易读,却在内部通过状态机实现了懒加载。

4. 自定义协程框架(无实验库)

如果你的编译器没有 std::experimental::generator,可以通过 std::coroutine_handle 手动实现一个简易的协程框架。下面给出一个最小可运行示例:

#include <coroutine>
#include <iostream>
#include <optional>

template<typename T>
struct generator {
    struct promise_type {
        std::optional <T> value_;
        std::coroutine_handle <promise_type> continuation_;

        generator get_return_object() {
            return generator{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept {
            if (continuation_) continuation_.resume();
            return {};
        }
        std::suspend_always yield_value(T value) {
            value_ = std::move(value);
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> coro_;
    generator(std::coroutine_handle <promise_type> h) : coro_(h) {}
    ~generator() { if (coro_) coro_.destroy(); }

    // 迭代器
    struct iterator {
        std::coroutine_handle <promise_type> coro_;
        bool done_;

        iterator(std::coroutine_handle <promise_type> h, bool d) : coro_(h), done_(d) {}

        T operator*() const { return *coro_.promise().value_; }

        iterator& operator++() {
            if (coro_) {
                coro_.promise().continuation_ = std::coroutine_handle <promise_type>::from_promise(coro_.promise());
                coro_.resume();
                done_ = !coro_ || coro_.promise().value_.has_value() == false;
            }
            return *this;
        }
        bool operator==(const iterator& other) const { return done_ == other.done_; }
        bool operator!=(const iterator& other) const { return !(*this == other); }
    };

    iterator begin() {
        if (coro_) {
            coro_.resume();
            bool done = !coro_ || !coro_.promise().value_.has_value();
            return iterator(coro_, done);
        }
        return iterator(nullptr, true);
    }

    iterator end() { return iterator(nullptr, true); }
};

generator <int> range_gen(int n) {
    for (int i = 1; i <= n; ++i) {
        co_yield i;
    }
}

int main() {
    for (auto v : range_gen(5)) {
        std::cout << v << ' ';
    }
    std::cout << '\n';
}

5. 协程与异步 I/O

协程最常见的用途是与异步 I/O 结合,例如 asio::awaitable(Boost.Asio)或 std::experimental::asynchronous。以下简化示例演示如何使用 std::future 和协程结合,模拟异步读取文件:

#include <iostream>
#include <fstream>
#include <future>
#include <string>
#include <coroutine>

struct async_file_reader {
    struct promise_type {
        std::string result_;
        std::coroutine_handle <promise_type> continuation_;

        async_file_reader get_return_object() {
            return async_file_reader{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept {
            if (continuation_) continuation_.resume();
            return {};
        }
        std::suspend_always yield_value(std::string line) {
            result_ += line + "\n";
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> coro_;
    async_file_reader(std::coroutine_handle <promise_type> h) : coro_(h) {}
    ~async_file_reader() { if (coro_) coro_.destroy(); }
    std::string get() { return coro_.promise().result_; }
};

async_file_reader read_file(const std::string& path) {
    std::ifstream in(path);
    std::string line;
    while (std::getline(in, line)) {
        co_yield line; // 非同步读取一行
    }
}

然后在主线程中使用:

int main() {
    auto reader = read_file("sample.txt");
    std::string content = reader.get();
    std::cout << content;
}

6. 结语

协程让异步代码写起来更像同步代码,逻辑更直观。C++20 标准库为协程提供了低级支持,配合 std::experimental::generator 或第三方库可以快速上手。未来 C++ 的协程模型将继续演进,支持更多编译器和平台,成为高性能系统编程的重要工具。祝你玩得开心,写出更高效、更可读的协程代码!

发表评论