C++20协程(co-routine)是对异步编程模式的一次重要升级,使得异步代码能够像同步代码一样直观、可读。本文将从协程的基本概念、实现原理到实际应用四个方面,带你系统了解如何在项目中使用C++协程。
1. 协程基础
1.1 什么是协程
协程是一种可挂起的函数,其执行可以在多次调用间切换状态。与线程不同,协程是轻量级的,并且只在单线程中切换,避免了线程切换的上下文成本。C++20 在标准库中新增了 std::coroutine_handle、std::suspend_always、std::suspend_never 等工具,提供了对协程生命周期的完整控制。
1.2 语法演示
#include <coroutine>
#include <iostream>
#include <string_view>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Task example() {
std::cout << "开始\n";
co_await std::suspend_always{};
std::cout << "继续\n";
}
int main() {
example();
}
这段代码展示了一个最简协程,使用 co_await 暂停执行。
2. 协程实现原理
协程的编译过程可以类比为状态机。编译器会把协程拆分成若干段,生成对应的状态机表。promise_type 保存协程的局部状态、异常信息等。协程在挂起时,调用 co_await 的 awaitable 对象会返回一个 await_suspend,该函数决定是否真正挂起。
2.1 promise_type 的角色
get_return_object:返回协程句柄(或包装对象)。initial_suspend/final_suspend:决定协程开始时和结束时是否挂起。return_void/return_value:处理返回值。unhandled_exception:处理异常。
2.2 awaitable 与 awaiter
协程中 co_await 的对象必须实现 await_ready、await_suspend、await_resume 三个成员函数。
await_ready:立即完成返回true或继续等待返回false。await_suspend:传入coroutine_handle,可以决定挂起行为。await_resume:等待结束后返回值。
3. 实际应用案例
3.1 异步网络请求
利用协程与 asio 或 libuv 结合,可实现简洁的异步 I/O。示例:
#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
using namespace boost::asio;
using namespace boost::asio::experimental::awaitable_ns;
awaitable <void> tcp_client(const std::string& host, unsigned short port) {
io_context& ioc = co_await this_coro::executor;
ip::tcp::resolver resolver(ioc);
auto endpoints = co_await resolver.async_resolve(host, std::to_string(port), use_awaitable);
ip::tcp::socket socket(ioc);
co_await async_connect(socket, endpoints, use_awaitable);
std::string msg = "Hello, server!";
co_await async_write(socket, buffer(msg), use_awaitable);
co_return;
}
此代码几乎与同步写法相同,却是非阻塞的。
3.2 延时任务
协程天然支持 co_await std::chrono::steady_clock::now() + std::chrono::seconds(2) 的方式实现延时。
awaitable <void> delay_task() {
auto start = std::chrono::steady_clock::now();
co_await std::chrono::steady_clock::now() + std::chrono::seconds(2);
std::cout << "延时2秒\n";
}
3.3 迭代器协程(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 return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> coro;
explicit generator(std::coroutine_handle <promise_type> h) : coro(h) {}
~generator() { if (coro) coro.destroy(); }
struct iterator {
std::coroutine_handle <promise_type> coro;
iterator(std::coroutine_handle <promise_type> h) : coro(h) {}
iterator& operator++() {
coro.resume();
if (coro.done()) coro = nullptr;
return *this;
}
bool operator==(std::default_sentinel_t) const { return !coro; }
const T& operator*() const { return coro.promise().current_value; }
};
iterator begin() {
coro.resume();
if (coro.done()) return iterator{nullptr};
return iterator{coro};
}
std::default_sentinel_t end() { return {}; }
};
generator <int> fib(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int tmp = a + b;
a = b;
b = tmp;
}
}
使用 for (int x : fib(10)) std::cout << x << ' '; 即可输出斐波那契数列。
4. 协程使用的注意事项
- 异常安全:
promise_type::unhandled_exception需要正确处理。 - 生命周期:协程句柄必须在其所持有的对象生命周期内有效。
- 性能:协程本身无上下文切换,但过度使用可能导致堆栈开销。
- 与多线程:协程本身是单线程的,若需要并发,请结合线程池或 async dispatch。
5. 结语
C++20 协程为异步编程带来了极大的便利,使得“异步”代码写法与同步代码保持一致的风格,极大提升了可读性与可维护性。通过正确理解协程的实现机制、熟悉 promise/promise_type 的使用,并结合现代异步 I/O 库,你可以在项目中快速构建高性能、低延迟的网络服务或其他异步功能。随着标准库和第三方生态的完善,协程将在 C++ 开发者的日常工作中发挥越来越重要的作用。