C++20中的协程:从概念到实践

协程(coroutine)是C++20标准新增的一项强大特性,它使得异步编程更加直观、易读。本文将从协程的基本概念入手,逐步讲解其实现细节、典型用法,并给出一个完整的示例,帮助读者快速掌握协程的核心思想与编程技巧。

1. 协程概念回顾

协程是一种轻量级的用户级线程,能够在函数执行过程中暂停(yield)并在后续继续执行,而不需要显式的线程切换。与传统的线程相比,协程在切换时不涉及上下文保存/恢复的成本,极大提升了并发性能。C++20将协程作为一种语言特性嵌入,使得协程的使用不再需要依赖第三方库。

2. 协程的核心组成

  • co_await:挂起协程,等待一个 awaitable 对象完成。
  • co_yield:将当前值返回给调用方,并挂起协程。
  • co_return:结束协程并返回最终结果。

3. Awaitable 对象

为了使一个对象可以被 co_await,它必须满足三个条件:

  1. operator co_await() 返回一个 awaiter。
  2. Awaiter 必须实现 await_ready(), await_suspend(), await_resume() 三个成员函数。
struct AsyncTimer {
    std::chrono::milliseconds duration;
    bool await_ready() const noexcept { return duration.count() == 0; }
    void await_suspend(std::coroutine_handle<> h) {
        std::thread([h, dur=duration]{
            std::this_thread::sleep_for(dur);
            h.resume();
        }).detach();
    }
    void await_resume() const noexcept {}
};

4. 协程返回类型:std::futuregenerator

  • **`std::future `**:用于一次性结果。
  • **`generator `**(需要自定义或使用 `cppcoro` 库):用于多值生成器。

C++20 标准库并未直接提供 generator,但我们可以自定义一个简单的实现:

template<typename T>
struct generator {
    struct promise_type {
        T current_value;
        std::suspend_always yield_value(T value) {
            current_value = value;
            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::terminate(); }
        void return_void() {}
    };

    using handle_type = std::coroutine_handle <promise_type>;
    handle_type coro;

    generator(handle_type h) : coro(h) {}
    ~generator() { if (coro) coro.destroy(); }

    struct iterator {
        handle_type coro;
        bool done = false;
        iterator(handle_type h) : coro(h) { ++(*this); }
        iterator& operator++() {
            coro.resume();
            done = !coro.done();
            return *this;
        }
        T operator*() const { return coro.promise().current_value; }
        bool operator==(std::default_sentinel_t) const { return done; }
    };

    iterator begin() { return iterator{coro}; }
    std::default_sentinel_t end() { return {}; }
};

5. 示例:异步读取文件并逐行输出

下面演示如何使用协程实现异步文件读取,模拟“逐行输出”的协程生成器。

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

// 1. Awaitable: async file read
struct AsyncReadLine {
    std::ifstream& stream;
    std::string line;
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) {
        std::thread([this, h]{
            if (std::getline(stream, line)) {
                h.resume();
            } else {
                h.resume(); // EOF handled in await_resume
            }
        }).detach();
    }
    std::string await_resume() { return line; }
};

// 2. generator <T>
template<typename T> struct generator;
template<typename T> struct generator {
    struct promise_type;
    using handle_type = std::coroutine_handle <promise_type>;
    handle_type coro;
    generator(handle_type h) : coro(h) {}
    ~generator() { if (coro) coro.destroy(); }
    struct promise_type {
        T current;
        generator get_return_object() {
            return generator{handle_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;
            return {};
        }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
    struct iterator {
        handle_type h;
        iterator(handle_type h_) : h(h_) { ++(*this); }
        iterator& operator++() { h.resume(); return *this; }
        T operator*() const { return h.promise().current; }
        bool operator==(std::default_sentinel_t) const { return h.done(); }
    };
    iterator begin() { return iterator{coro}; }
    std::default_sentinel_t end() { return {}; }
};

// 3. 协程函数
generator<std::string> async_read_file(std::string filename) {
    std::ifstream file(filename);
    if (!file.is_open()) co_return;
    while (!file.eof()) {
        std::string line = co_await AsyncReadLine{file};
        if (line.empty() && file.eof()) break;
        co_yield line;
    }
}

int main() {
    auto gen = async_read_file("example.txt");
    for (const auto& line : gen) {
        std::cout << line << '\n';
    }
    return 0;
}

说明

  • AsyncReadLine 在后台线程中读取一行,完成后恢复协程。
  • generator 模板实现了一个可迭代的协程生成器。
  • async_read_file 使用 co_yield 逐行返回文件内容。

6. 性能与注意事项

  1. 上下文切换成本:协程在 co_await 挂起时会产生一次上下文切换,若使用线程池等方式实现 awaiter,可显著降低成本。
  2. 异常传播:协程内部抛出的异常会被 promise_type::unhandled_exception 捕获,默认调用 std::terminate,可自定义处理逻辑。
  3. 资源管理:协程完成后需手动销毁或使用 RAII;std::coroutine_handledestroy() 必不可少。

7. 小结

C++20 的协程提供了更简洁的异步编程模型,避免了回调地狱、状态机手写等繁琐过程。掌握 co_awaitco_yield 与 awaitable 的实现细节,可以让你在高并发、IO 密集型场景下写出高效、可维护的代码。希望本文能为你开启协程之路。

发表评论