协程(coroutine)是C++20标准新增的一项强大特性,它使得异步编程更加直观、易读。本文将从协程的基本概念入手,逐步讲解其实现细节、典型用法,并给出一个完整的示例,帮助读者快速掌握协程的核心思想与编程技巧。
1. 协程概念回顾
协程是一种轻量级的用户级线程,能够在函数执行过程中暂停(yield)并在后续继续执行,而不需要显式的线程切换。与传统的线程相比,协程在切换时不涉及上下文保存/恢复的成本,极大提升了并发性能。C++20将协程作为一种语言特性嵌入,使得协程的使用不再需要依赖第三方库。
2. 协程的核心组成
co_await:挂起协程,等待一个 awaitable 对象完成。co_yield:将当前值返回给调用方,并挂起协程。co_return:结束协程并返回最终结果。
3. Awaitable 对象
为了使一个对象可以被 co_await,它必须满足三个条件:
- 有
operator co_await()返回一个 awaiter。 - 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::future 与 generator
- **`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. 性能与注意事项
- 上下文切换成本:协程在
co_await挂起时会产生一次上下文切换,若使用线程池等方式实现 awaiter,可显著降低成本。 - 异常传播:协程内部抛出的异常会被
promise_type::unhandled_exception捕获,默认调用std::terminate,可自定义处理逻辑。 - 资源管理:协程完成后需手动销毁或使用 RAII;
std::coroutine_handle的destroy()必不可少。
7. 小结
C++20 的协程提供了更简洁的异步编程模型,避免了回调地狱、状态机手写等繁琐过程。掌握 co_await、co_yield 与 awaitable 的实现细节,可以让你在高并发、IO 密集型场景下写出高效、可维护的代码。希望本文能为你开启协程之路。