C++20中的协程:从基础到实践

协程(coroutine)是 C++20 引入的一项强大功能,旨在简化异步编程和生成器的实现。与传统的回调或线程相比,协程通过暂停和恢复执行,允许程序在保持单线程执行的同时实现类似多线程的并发效果。本文将从协程的基本概念、关键语法到实际应用,带你快速入门。

一、协程基本概念

协程可以被视为“轻量级线程”,它们能够在执行过程中暂停(co_awaitco_yieldco_return),随后恢复。核心特性包括:

  • 挂起点(suspend point):程序在此停留,等待外部条件或事件。
  • 恢复点(resume point):挂起后再次激活,继续执行。
  • 协程句柄(std::coroutine_handle:控制协程生命周期的对象。

协程本质上是生成器与异步操作的统一表达方式。

二、关键语法

  1. 协程函数声明
std::generator <int> my_generator() {
    for (int i = 0; i < 5; ++i) {
        co_yield i;  // 产生值
    }
}
  • `std::generator ` 是 C++20 中提供的协程返回类型,表示一个可迭代的生成器。
  • co_yield 暂停执行,返回当前值;后续迭代会恢复。
  1. 异步协程
std::future <int> async_add(int a, int b) {
    co_return a + b; // 立即返回值,异步上下文会自动包装为 std::future
}
  • co_return 结束协程,生成一个 `std::future `。
  1. 挂起与恢复
std::generator <int> range(int start, int end) {
    for (int i = start; i <= end; ++i) {
        co_yield i;
    }
}

调用方:

for (int v : range(1, 10)) {
    std::cout << v << ' ';
}

三、实现自定义生成器

虽然 std::generator 已提供,但在某些场景需要自定义协程返回类型。以下示例展示如何实现一个简单的整数生成器。

#include <coroutine>
#include <iostream>

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 return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

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

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

    generator(const generator&) = delete;
    generator& operator=(const generator&) = delete;
    generator(generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; }
    generator& operator=(generator&& other) noexcept {
        if (this != &other) {
            if (coro) coro.destroy();
            coro = other.coro;
            other.coro = nullptr;
        }
        return *this;
    }

    T next() {
        coro.resume();
        return coro.promise().current_value;
    }

    bool done() const { return coro.done(); }
};

使用示例:

generator <int> seq() {
    for (int i = 0; i < 5; ++i) {
        co_yield i * 2;
    }
}

int main() {
    auto g = seq();
    while (!g.done()) {
        std::cout << g.next() << ' ';
    }
}

四、协程在异步 I/O 的应用

协程最常见的场景之一是网络 I/O。假设我们使用一个异步 I/O 库(如 Boost.Asio 或 std::net),协程可大幅简化代码:

#include <asio.hpp>
#include <iostream>
#include <coroutine>

asio::awaitable <void> handle_client(tcp::socket socket) {
    try {
        char data[1024];
        std::size_t n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);
        std::cout << "Received: " << std::string(data, n) << std::endl;
        co_return;
    } catch (std::exception& e) {
        std::cerr << "Error: " << e.what() << '\n';
    }
}

这里 co_await 暂停协程,直到 I/O 完成。整个流程保持同步可读性,同时不阻塞线程。

五、协程的性能与注意事项

  • 栈分配:协程的栈由实现决定,通常是动态分配的,避免了大栈的使用。
  • 生命周期:协程句柄必须在协程结束前保持有效;使用 std::generator 时,迭代器内部已管理。
  • 异常传播:异常会被 promise_type::unhandled_exception() 捕获,可自定义异常处理。
  • 与线程混用:协程本身不创建线程,需与线程池或事件循环结合使用。

六、总结

C++20 协程为开发者提供了统一、高效的异步编程模型。无论是生成器、异步 I/O,还是复杂的协程链式调用,都能在保持代码可读性的同时提升性能。通过本文的示例,你已掌握协程的基本语法、实现方式以及常见应用场景。下一步可以尝试将协程与多线程池、任务调度器结合,进一步探索更高级的并发架构。祝编码愉快!

发表评论