C++20协程与并发编程:从基础到实践

C++20引入了协程(coroutines)这一强大的语言特性,极大地方便了异步编程与并发控制。与传统的线程或回调相比,协程提供了更清晰的语义、更低的资源占用以及更好的可组合性。本文将从协程的基本概念讲起,展示其与并发编程的结合,并给出完整示例代码。

  1. 协程的基本概念
    协程本质上是一种“轻量级线程”,能够在执行中断点暂停并在需要时恢复。C++20使用co_awaitco_yieldco_return等关键字来声明协程,编译器会将其展开为状态机。

  2. 协程与线程的区别

    • 资源占用:协程在栈上只占用几百字节,线程需要几百KB到1MB。
    • 调度方式:协程由程序显式调度,线程由操作系统调度。
    • 同步方式:协程天然支持异步等待,线程往往需要使用锁或条件变量。
  3. 协程与异步I/O
    在C++20标准库中,std::futurestd::promise仍是最常用的异步工具。协程通过co_await等待std::future完成,代码像同步一样可读。

  4. 协程实现的并发任务池
    为了在多核上并行执行协程任务,可以结合线程池与协程。下面给出一个简易的任务池示例:

    #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)包装成可被线程池调度的任务。每个协程生成一系列结果,线程池中的线程负责执行并打印。

  5. 协程的性能考量

    • 栈大小:协程的栈是编译器管理的,默认很小,适合嵌套深度不大的情况。
    • 上下文切换:协程切换成本低于线程切换,但仍需避免过度细粒度的切换。
    • 与IO的配合:协程最适合与非阻塞IO结合,配合ASIO或boost::asio可实现高效网络编程。
  6. 总结
    C++20的协程为并发编程提供了更简洁、更易维护的方案。结合线程池、事件循环或异步IO,开发者可以在保持代码可读性的同时,充分利用多核并行。随着标准库的完善与编译器优化,协程将成为未来C++并发编程的主流工具。

发表评论