从C++20的协程到并发编程:一种全新思路

协程(coroutine)在C++20标准中正式引入,为传统的同步和异步编程带来了革命性的变化。相较于传统的线程和回调机制,协程以轻量级、可读性强、资源占用低的方式,实现了非阻塞的执行流。本文将从协程的基本概念入手,讲解如何使用C++20协程实现一个异步任务调度器,并进一步探讨其在并发编程中的优势与应用场景。

1. 协程基础

C++20中的协程是由 co_awaitco_yieldco_return 这三个关键字实现的。协程函数的返回类型必须是可被 co_await 的对象,常见的如 std::futurestd::generator 或自定义的 awaitable

std::future <int> async_add(int a, int b) {
    co_return a + b;          // 直接返回结果
}

上面代码会在内部生成一个状态机,co_return 触发协程结束并把结果包装成 `std::future

`。 ## 2. 构建一个简单的任务调度器 ### 2.1 任务包装 首先我们需要一个通用的 `Task` 类型,包装任何可被 `co_await` 的协程。 “`cpp template class Task { public: struct promise_type { T value_; std::exception_ptr exception_; std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } Task get_return_object() { return Task{ std::coroutine_handle ::from_promise(*this) }; } void unhandled_exception() { exception_ = std::current_exception(); } void return_value(T val) { value_ = std::move(val); } }; using handle_type = std::coroutine_handle ; explicit Task(handle_type h) : handle_(h) {} Task(const Task&) = delete; Task(Task&& other) : handle_(other.handle_) { other.handle_ = nullptr; } ~Task() { if (handle_) handle_.destroy(); } T get() { if (handle_.promise().exception_) std::rethrow_exception(handle_.promise().exception_); return std::move(handle_.promise().value_); } private: handle_type handle_; }; “` ### 2.2 事件循环 为了在单线程中实现并发,需要一个事件循环。下面是一个最简版的事件循环,它会执行所有可就绪的任务。 “`cpp class EventLoop { public: void run() { while (!tasks_.empty()) { auto task = std::move(tasks_.front()); tasks_.pop_front(); task(); // 触发协程 } } void schedule(Task task) { tasks_.push_back(std::move(task)); } private: std::deque> tasks_; }; “` ### 2.3 例子:异步IO模拟 假设我们需要模拟一个异步IO操作,例如读取文件。我们用 `std::this_thread::sleep_for` 来表示等待。 “`cpp Task async_read(int bytes) { // 模拟异步等待 std::this_thread::sleep_for(std::chrono::milliseconds(100)); co_return bytes; // 读取成功,返回读取字节数 } “` ## 3. 协程与线程的比较 | 特点 | 线程 | 协程 | |——|——|——| | 开销 | 高(栈、上下文切换) | 低(仅存储状态) | | 可读性 | 代码顺序性差 | 代码顺序性好 | | 并发模型 | 多核共享 | 单核协作 | | 错误处理 | 传统异常/信号 | 统一异常捕获 | 协程通过在单线程中切换执行点,天然避免了锁竞争和死锁问题。但在多核并行加速场景下,仍需配合多线程或分布式方案。 ## 4. 实际应用场景 1. **网络服务器**:基于协程的网络框架(如 cpp-httplib、Boost.Asio 的协程模式)能让每个请求占用极小的栈空间,提升吞吐量。 2. **游戏引擎**:协程可以实现游戏逻辑的“顺序执行”,如动画、物理模拟,减少回调地狱。 3. **数据处理管道**:使用 `co_yield` 可以构建生成器式的数据流,实现按需消费、惰性计算。 ## 5. 小结 C++20协程为并发编程带来了全新的视角。通过协程与事件循环相结合,我们可以在单线程中实现高并发、低延迟的异步程序。虽然协程在多核并行方面并不直接提供加速,但它们可以作为更高级并发抽象的基础,帮助程序员写出更简洁、易维护的代码。 从现在起,尝试用协程重写你手中的回调网络代码,感受一下“暂停”和“恢复”给代码结构带来的改变吧。

发表评论