**如何使用C++20协程简化异步编程**

在传统的 C++ 异步编程中,回调函数、状态机、以及手动管理资源往往让代码变得臃肿且难以维护。自从 C++20 引入协程(coroutine)以来,编写清晰、直观的异步代码变得前所未有地简单。本文将从协程的基本概念、关键字到实际示例,详细阐述如何利用 C++20 协程实现高效的异步 I/O 与任务调度。


1. 协程的核心概念

1.1 协程与线程的区别

  • 线程:操作系统级别的并发单元,切换开销大。
  • 协程:用户级的轻量级协作式调度,切换成本极低,只需要保存和恢复堆栈指针。

1.2 协程的基本术语

术语 定义
promise_type 协程函数返回值类型的内部实现,用来传递状态与异常。
suspend_always / suspend_never 控制协程的挂起与恢复行为。
awaitable 表示可以被 co_await 的对象。

2. C++20 协程的语法要点

co_return          // 返回值,转交给 promise_type
co_yield            // 在 coroutine 中产生值
co_await            // 等待 awaitable 对象完成

2.1 协程函数定义

std::future <int> asyncAdd(int a, int b) {
    co_return a + b;
}
  • std::future 内部实现了协程的 promise_type,因此直接使用即可。

2.2 自定义 awaitable

struct Waitable {
    std::chrono::milliseconds ms;
    bool await_ready() const noexcept { return ms.count() == 0; }
    void await_suspend(std::coroutine_handle<> h) const noexcept {
        std::thread([=, h]() {
            std::this_thread::sleep_for(ms);
            h.resume(); // 继续协程
        }).detach();
    }
    void await_resume() const noexcept {}
};

使用方式:

async void example() {
    std::cout << "Start\n";
    co_await Waitable{ std::chrono::milliseconds(1000) };
    std::cout << "After 1s\n";
}

3. 实战:异步文件读取

#include <iostream>
#include <filesystem>
#include <fstream>
#include <coroutine>
#include <vector>
#include <string>
#include <thread>

struct FileReadAwaitable {
    std::string path;
    std::vector <char> buffer;
    bool await_ready() const noexcept { return false; }

    void await_suspend(std::coroutine_handle<> h) const noexcept {
        std::thread([=, h]() {
            std::ifstream file(path, std::ios::binary);
            if (file) {
                file.seekg(0, std::ios::end);
                size_t size = file.tellg();
                buffer.resize(size);
                file.seekg(0, std::ios::beg);
                file.read(buffer.data(), size);
            }
            h.resume();
        }).detach();
    }

    const std::vector <char>& await_resume() const noexcept { return buffer; }
};

struct AsyncFileReader {
    struct promise_type {
        AsyncFileReader get_return_object() {
            return AsyncFileReader{
                std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };

    std::coroutine_handle <promise_type> handle;
    ~AsyncFileReader() {
        if (handle) handle.destroy();
    }
};

AsyncFileReader read_file_async(const std::string& path) {
    FileReadAwaitable awaitable{path, {}};
    auto buffer = co_await awaitable;
    std::cout << "File size: " << buffer.size() << " bytes\n";
    co_return;
}

int main() {
    auto reader = read_file_async("example.txt");
    // 在主线程中做其他事情...
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}

说明

  • FileReadAwaitable 将文件读取包装为 awaitable,后台线程完成 I/O,然后在主线程恢复协程。
  • AsyncFileReader 通过 promise_type 管理协程生命周期,保证资源得到正确释放。

4. 协程与任务调度器

如果想让多个协程并行执行并共享线程池,可以使用简单的任务调度器。

#include <queue>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <thread>

class ThreadPool {
public:
    ThreadPool(size_t n) : stop(false) {
        workers.reserve(n);
        for (size_t i = 0; i < n; ++i)
            workers.emplace_back([this] { this->worker(); });
    }

    ~ThreadPool() {
        {
            std::unique_lock lock(m);
            stop = true;
            cv.notify_all();
        }
        for (auto& t : workers) t.join();
    }

    template<class F> void enqueue(F&& f) {
        {
            std::unique_lock lock(m);
            tasks.emplace(std::forward <F>(f));
        }
        cv.notify_one();
    }

private:
    void worker() {
        while (true) {
            std::function<void()> task;
            {
                std::unique_lock lock(m);
                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 m;
    std::condition_variable cv;
    bool stop;
};

协程与线程池结合

// 将 awaitable 的 await_suspend 改为将恢复操作提交到线程池
struct AsyncSleep {
    std::chrono::milliseconds ms;
    ThreadPool& pool;
    void await_suspend(std::coroutine_handle<> h) const noexcept {
        pool.enqueue([=, h]() {
            std::this_thread::sleep_for(ms);
            h.resume();
        });
    }
};

5. 性能与调优

方面 建议
堆栈大小 协程默认使用 8KB 的栈,若递归深度大可手动指定更大堆栈。
异常处理 promise_type::unhandled_exception 可以捕获并转换为 std::exception_ptr,防止程序崩溃。
资源清理 使用 final_suspend 返回 std::suspend_always,确保协程在结束前有机会执行清理代码。

6. 结语

C++20 协程为异步编程提供了强大的语义层次与简洁语法,使得复杂的异步逻辑可以像同步代码一样书写。虽然协程本身的底层实现仍然需要注意资源管理和线程调度,但只要掌握基本模式,即可在网络、文件 I/O、定时任务等领域快速构建高性能、可维护的系统。希望本文能帮助你在日常项目中更好地运用协程,解锁 C++ 异步编程的无限潜能。

发表评论