C++20引入了协程(coroutines)这一强大的语言特性,极大地方便了异步编程与并发控制。与传统的线程或回调相比,协程提供了更清晰的语义、更低的资源占用以及更好的可组合性。本文将从协程的基本概念讲起,展示其与并发编程的结合,并给出完整示例代码。
-
协程的基本概念
协程本质上是一种“轻量级线程”,能够在执行中断点暂停并在需要时恢复。C++20使用co_await、co_yield、co_return等关键字来声明协程,编译器会将其展开为状态机。 -
协程与线程的区别
- 资源占用:协程在栈上只占用几百字节,线程需要几百KB到1MB。
- 调度方式:协程由程序显式调度,线程由操作系统调度。
- 同步方式:协程天然支持异步等待,线程往往需要使用锁或条件变量。
-
协程与异步I/O
在C++20标准库中,std::future、std::promise仍是最常用的异步工具。协程通过co_await等待std::future完成,代码像同步一样可读。 -
协程实现的并发任务池
为了在多核上并行执行协程任务,可以结合线程池与协程。下面给出一个简易的任务池示例:#include <coroutine> #include <vector> #include <thread> #include <queue> #include <condition_variable> #include <iostream> // 简单的协程生成器 struct Generator { struct promise_type; using handle_type = std::coroutine_handle <promise_type>; struct promise_type { int current_value; std::suspend_always yield_value(int value) { current_value = value; return {}; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } Generator get_return_object() { return {handle_type::from_promise(*this)}; } void unhandled_exception() { std::terminate(); } void return_void() {} }; handle_type coro; Generator(handle_type h) : coro(h) {} ~Generator() { if (coro) coro.destroy(); } bool next() { coro.resume(); return !coro.done(); } int value() { return coro.promise().current_value; } }; // 线程池 class ThreadPool { public: ThreadPool(size_t n) : stop_(false) { for (size_t i = 0; i < n; ++i) workers_.emplace_back([this] { this->worker(); }); } ~ThreadPool() { { std::unique_lock<std::mutex> lock(mtx_); stop_ = true; cv_.notify_all(); } for (auto &t : workers_) t.join(); } template<typename F> void enqueue(F&& f) { { std::unique_lock<std::mutex> lock(mtx_); tasks_.emplace(std::forward <F>(f)); } cv_.notify_one(); } private: void worker() { while (true) { std::function<void()> task; { std::unique_lock<std::mutex> lock(mtx_); cv_.wait(lock, [this] { return stop_ || !tasks_.empty(); }); if (stop_ && tasks_.empty()) return; task = std::move(tasks_.front()); tasks_.pop(); } task(); } } std::vector<std::thread> workers_; std::queue<std::function<void()>> tasks_; std::mutex mtx_; std::condition_variable cv_; bool stop_; }; // 协程任务 Generator task(int id) { for (int i = 0; i < 5; ++i) { co_yield id * 10 + i; // 模拟工作 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟 I/O } } int main() { ThreadPool pool(4); // 4 个线程 std::vector <Generator> gens; for (int i = 1; i <= 8; ++i) { gens.emplace_back(task(i)); } for (auto &g : gens) { pool.enqueue([&g] { while (g.next()) { std::cout << "Result: " << g.value() << "\n"; } }); } // 等待所有任务完成 std::this_thread::sleep_for(std::chrono::seconds(3)); return 0; }该示例演示了如何将协程(
Generator)包装成可被线程池调度的任务。每个协程生成一系列结果,线程池中的线程负责执行并打印。 -
协程的性能考量
- 栈大小:协程的栈是编译器管理的,默认很小,适合嵌套深度不大的情况。
- 上下文切换:协程切换成本低于线程切换,但仍需避免过度细粒度的切换。
- 与IO的配合:协程最适合与非阻塞IO结合,配合ASIO或boost::asio可实现高效网络编程。
-
总结
C++20的协程为并发编程提供了更简洁、更易维护的方案。结合线程池、事件循环或异步IO,开发者可以在保持代码可读性的同时,充分利用多核并行。随着标准库的完善与编译器优化,协程将成为未来C++并发编程的主流工具。