在 C++20 之前,协程(coroutines)在 C++ 标准库中并未正式加入,但借助 GCC 和 Clang 的实验性扩展,开发者已经可以在 C++17 环境下实现协程功能。本文将介绍协程的基本概念、如何在 C++17 环境下使用它们,以及一个简单的生产者-消费者示例。
1. 协程基础
协程是可暂停的函数,能够在执行过程中多次挂起并恢复,从而实现异步编程、生成器等功能。相比传统的线程,协程占用更少资源,并且可以在单线程内完成多任务协作。
在 C++20 标准中,协程语法通过 co_await, co_yield 和 co_return 三个关键字实现。然而在 C++17 的实验性实现中,可以使用 std::experimental::coroutine 提供的 API 来手动编写协程。
2. 环境准备
- GCC 9+ 或 Clang 10+:这两个编译器已实现
std::experimental::coroutine。 - 编译选项:在编译时需要开启实验性协程支持:
g++ -std=c++17 -fcoroutines -Wall -O2 main.cpp -o main或者
clang++ -std=c++17 -fcoroutines -Wall -O2 main.cpp -o main
3. 关键组件
3.1 promise_type
每个协程都有一个对应的 promise_type,用于管理协程的生命周期和返回值。示例 generator:
template<typename T>
struct generator {
struct promise_type;
using handle_type = std::experimental::coroutine_handle <promise_type>;
struct promise_type {
T current_value;
std::exception_ptr exception;
generator get_return_object() {
return generator(handle_type::from_promise(*this));
}
std::experimental::suspend_always initial_suspend() { return {}; }
std::experimental::suspend_always final_suspend() noexcept { return {}; }
std::experimental::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void unhandled_exception() { exception = std::current_exception(); }
void return_void() {}
};
handle_type coro;
generator(handle_type h) : coro(h) {}
~generator() { if (coro) coro.destroy(); }
struct iterator {
handle_type coro;
bool done;
iterator(handle_type h, bool d) : coro(h), done(d) {}
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() {
coro.resume();
return iterator(coro, coro.done());
}
std::default_sentinel_t end() { return {}; }
};
3.2 使用协程
下面的例子演示了一个简单的整数生成器:
generator <int> count_to(int n) {
for (int i = 1; i <= n; ++i) {
co_yield i; // 挂起并返回当前值
}
}
在主函数中可以像遍历普通容器一样使用:
int main() {
for (auto x : count_to(5)) {
std::cout << x << " ";
}
// 输出: 1 2 3 4 5
}
4. 生产者-消费者示例
下面给出一个使用协程实现的生产者-消费者模型,展示协程与同步机制的结合。
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <experimental/coroutine>
using namespace std::experimental;
// 生产者生成整数
generator <int> producer(int count, int delay_ms) {
for (int i = 0; i < count; ++i) {
// 模拟耗时工作
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
co_yield i; // 产生一个值
}
}
// 消费者从队列读取
void consumer(std::queue <int>& q, std::mutex& m, std::condition_variable& cv, bool& done) {
while (true) {
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&]{ return !q.empty() || done; });
while (!q.empty()) {
int val = q.front(); q.pop();
std::cout << "Consumed: " << val << std::endl;
}
if (done) break;
}
}
int main() {
std::queue <int> q;
std::mutex m;
std::condition_variable cv;
bool done = false;
// 启动消费者线程
std::thread consumer_thread(consumer, std::ref(q), std::ref(m), std::ref(cv), std::ref(done));
// 生产者协程
for (int val : producer(10, 100)) {
{
std::lock_guard<std::mutex> lock(m);
q.push(val);
}
cv.notify_one();
}
// 通知消费者结束
{
std::lock_guard<std::mutex> lock(m);
done = true;
}
cv.notify_one();
consumer_thread.join();
return 0;
}
5. 小结
- C++17 已经通过实验扩展提供了协程支持,使用
-fcoroutines开关即可启用。 - 关键在于实现
promise_type并掌握co_yield/co_return的使用方式。 - 协程非常适合实现生成器、事件循环和轻量级异步任务,可显著简化代码逻辑。
- 在实际项目中,可以与线程、同步原语结合,构建高性能、可维护的异步框架。
希望本文能帮助你在 C++17 环境中顺利使用协程,开启更高效的编程之路。