在现代 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++ 的协程模型将继续演进,支持更多编译器和平台,成为高性能系统编程的重要工具。祝你玩得开心,写出更高效、更可读的协程代码!