C++20——协程

目录

前言

什么是协程

协程的三要素

Promise对象

协程句柄

[Awaitable 对象](#Awaitable 对象)

样例一:生成器

样例二:异步线程恢复

样例三:echo服务器

面试题:


前言

最近在学习协程,学完后计划整理一份复习资料,帮助自己更好的掌握知识点,应对面试。

什么是协程

一种可以暂停执行并恢复执行的函数。与普通函数的 "执行完才退出" 不同,协程可以在执行中途挂起,保存当前所有上下文,之后从挂起处恢复。

普通函数像坐飞机,从起点直飞终点,中间不能停。协程像高铁,可以在沿途各个站点上下客、停靠,然后继续行驶。

当⼀个函数包含 co_await/co_yield/co_return 任意⼀个关键字时,它就是协程。

  • co_await expression 用于暂停执行直到恢复
  • co_yield expression 用于暂停执行并返回⼀个值
  • co_return expression 用于完成执行并返回⼀个值

C++20 的协程是无栈协程,这意味着暂停时只保存协程帧(程序计数器、局部对象、Promise对象

等),而不是整个调用栈。这使得它们非常轻量,但意味着在协程内暂停时,其调用者也会被暂停。

协程的三要素

Promise对象

协程的 "管家"。负责定义协程的行为,比如如何返回值、如何抛出异常、何时开始、结束。

Promise 类型必须提供以下核心接口,用来控制协程的行为。

get_return_object()

  • 作用:创建并返回给调用者协程的返回值。这是在协程函数体执行之前被调用的。
  • 返回值类型:协程的返回类型(例如外层的Task、Generator)。

initial_suspend()

  • 作用:决定协程在开始执行函数体之前是否应该先挂起。
  • 返回值:⼀个满足Awaitable 概念的对象(通常是 std::suspend_always 或 std::suspend_never )。
  • std::suspend_always{} : 总是挂起。这意味着协程⼀创建就是惰性的,不会自动开始执行,需要手动 resume() ,这是"懒汉式"协程的常见选择。
  • std::suspend_never{} : 从不挂起。协程创建后会立即开始执行,直到遇到第⼀个挂起点。这是"饿汉式"协程的常见选择。

final_suspend()

  • 作用:决定协程在执行完函数体(或通过 co_return 返回)后,在彻底销毁自身之前是否应该挂起。
  • 返回值:同样是⼀个 Awaitable 概念对象。(通常是 std::suspend_always 或 std::suspend_never )。
  • 如果返回 std::suspend_always ,你必须在协程外部某处手动调用.destroy() 来销毁协程帧,否则会发生内存泄漏。如果返回 std::suspend_never ,协程会在返回后自动清理自己。

return_void() 和 return_value(...)

  • 作用:处理 co_return 语句。
  • Promise 类型必须实现这两个的其中之⼀,但不能同时实现。
  • void return_void() : 用于处理 co_return; (无返回值)。
  • void return_value(type value) : 用于处理 co_return value; (有返回值)。 value 的类型由使用者指定,通常使用者会把它存储到 Promise 的⼀个成员变量中,以便外部获取。

yield_value(...)

  • 作用:处理 co_yield 语句。 co_yield expression 本质上被编译器翻译为 co_await promise.yield_value(expression) 。
  • 返回值:⼀个 Awaitable 概念对象,用于决定在产出值之后是否要挂起协程(通常返回 std::suspend_always{} 来挂起,让调⽤者有机会处理产出的值)。

unhandled_exception()

  • 作用:当协程体内发生异常,并且该异常未被协程体内的 try/catch 块捕获时,这个异常会被自动传递给 unhandled_exception() 方法。
  • unhandled_exception() : Promise 类型的核心异常处理函数,协程内任何未捕获的异常都会调用此函数,std::current_exception() : 捕获当前异常并转换为 std::exception_ptr,std::rethrow_exception() : 重新抛出存储的异常。
  • RAII 模式在协程中依然有效,即使协程抛出异常,栈上的对象也会正确析构,协程帧本⾝的内存由 coroutine_handle 管理。

协程句柄

std::coroutine_handle<>。协程的 "遥控器"。用于恢复协程执行、销毁协程、检查是否挂起。

协程句柄的核心操作 API 主要有以下几个:

from_promise()/promise()

  • static coroutine_handle from_promise(Promise& p ); ,用于使用⼀个 Promise 对象创建⼀个指向 Promise 对象的句柄,通常在 Promise 类型的 get_return_object 成员函数中使用。
  • Promise& promise() const; 方便我们使用句柄可以获取管理的 Promise 对象。

resume()/operator()

  • 作用:让⼀个处于挂起状态的协程继续执行。
  • 行为:调用后,协程从上次挂起的地方开始执行,直到遇到下⼀个挂起点、结束或抛出异常。
  • 注意:不能在非挂起状态的协程上调用(如已运行完成或尚未开始的协程,行为未定义)。

done()

  • 作用:查询协程是否已执行完成(即是否已经到达函数体末尾或执行了 co_return )。
  • 返回值: true : 协程已执行完成, false : 协程仍在运行或处于挂起状态。

operator bool()

  • 作用:查询是否是⼀个有效的协程句柄。
  • 返回值: true : 指向⼀个有效的协程, false : 协程句柄内存指向⼀个 nullptr 指针。

destroy()

  • 作用:显式销毁协程帧,释放其内存。
  • 何时调用:通常在协程返回类型的析构中调用,只有当协程在 final_suspend() 返回std::suspend_always 挂起时,才需要(且必须)手动调用.destroy() 。如果 final_suspend() 返回 std::suspend_never ,协程会自动销毁自己,再调用.destroy() 就是未定义行为。

operator coroutine_handle<>()

  • 作用:该转换函数将 std::coroutine_handle<Promise> 值转换为包含相同底层 address 的 std::coroutine_handle<> 值。
  • 何时调用: void await_suspend(std::coroutine_handle<> h) 中作为形参接收各协程的句柄。

Awaitable 对象

协程的 "等待条件"。定义协程何时挂起、何时恢复(例如等待 IO 完成、等待一个结果)。

⼀个类型要成为 Awaitable,它必须实现三个特定的成员函数await_ready/await_suspend/await_resume ,或者通过 operator co_await 重载返回⼀个实现了这些函数的对象。

bool await_ready() const

  • 目的:性能优化。在尝试暂停之前,检查异步操作是否已经完成。
  • 返回值: true :表示结果已就绪,无需暂停协程。编译器将跳过 await_suspend 和暂停步骤,直接调用 await_resume 并继续执行。 false :表示结果未就绪,需要暂停协程,继续执行await_suspend 逻辑。
  • 最佳实践通常实现为 noexcept 。对于立即完成的操作(如缓存命中),返回 true 可以避免不必要的暂停开销。

type await_suspend( std::coroutine_handle<>) const

  • 这是最强大也是最复杂的部分,它在协程即将暂停但尚未暂停时被调用。
  • 目的:安排异步操作的回调,它的职责是获取当前协程的句柄 handle ,并将其传递给某个异步操作,以便在操作完成后恢复协程。
  • 参数: std::coroutine_handle<> ,代表当前正在执行的协程,你可以保存它、传递它
  • 返回值和行为:返回值类型至关重要,它决定了控制流的走向
  • 返回值行为 void ,默认行为,协程暂停,控制权返回到当前协程调用者。这是最常见的情 况。
  • 返回类型为 bool , true 表示协程已挂起到后台(可能在另⼀个线程恢复); false 表示不挂起,立即恢复,用于在最后关头发现需要等待的操作已完成的情况。
  • 返回类型为 std::coroutine_handle<> ,另⼀个协程句柄 handle 协程暂停,然后立即恢复 handle 所代表的协程,这实现了对称转移,是无栈协程链式调用的关键。
  • 最佳实践: await_suspend 通常实现为 noexcept ,在这里通常会调用某个异步 API,并将 handle 作为其完成回调,绝对不要在此函数内阻塞。

type await_resume() const

  • 目的:获取结果或处理错误。在协程恢复后或 await_ready 返回 true 时,此函数被调用以产生co_await 表达式的结果。
  • 返回值:可以是 void 或任何其他类型,这个返回值就是整个 co_await 表达式的结果。
  • 如果异步操作产生⼀个值, await_resume() 返回这个值。
  • 如果异步操作可能失败, await_resume() 可以检查错误码并选择返回⼀个值或抛出⼀个异 常。
  • 最佳实践:可以抛出异常,不抛异常时标记为 noexcept 。

C++20 在标准库中提供了两个最简单的 Awaitable 类型。

std::suspend_always

  • await_ready() : 返回 false ,总是暂停。
  • await_suspend(...) : 无操作,直接返回 void。
  • await_resume() : 返回 void。
  • 用途:用于 initial_suspend() 和 final_suspend() ,表示请在此处暂停。

std::suspend_never

  • await_ready() : 返回 true ,从不暂停。
  • await_suspend(...) : 永远不会被调用。
  • await_resume() : 返回 void 。
  • 用途:用于 initial_suspend() ,表示请不要暂停,立即开始执行协程体。

样例一:生成器

复制代码
#include <coroutine>
#include <exception>
#include <iostream>
// 1. 协程的返回类型
struct Generator {
    // 2. 核心:promise_type
    struct promise_type {
        int current_value; // 用于存储产出的值

        // 2a. 创建返回值对象
        Generator get_return_object() {
            return Generator{
            std::coroutine_handle<promise_type>::from_promise(*this) };
        }

        // 2b. 初始挂起:选择挂起,让协程惰性执行
        std::suspend_always initial_suspend() { return {}; }

        // 2c. 最终挂起:选择挂起,我们需要手动销毁
        std::suspend_always final_suspend() noexcept { return {}; }

        // 2d. 处理 co_yield
        std::suspend_always yield_value(int value) {
            current_value = value;
            return {}; // 产出后总是挂起
        }

        // 2e. 处理 co_return; (无值返回)
        void return_void() {}

        // 2f. 处理异常
        void unhandled_exception() { std::terminate(); }
    };

    // 3. Generator 类本身的成员
    // 协程句柄,用于从外部控制协程
    std::coroutine_handle<promise_type> handle;

    // 构造函数和析构函数
    explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() {
        if (handle) handle.destroy(); // 负责销毁协程帧
    }

    // 4. 提供给外部的API
    // 获取当前值
    int value() const {
        return handle.promise().current_value;
    }

    // 恢复执行直到下⼀个co_yield或结束
    bool next() {
        if (!handle.done()) {
            handle.resume();
        }
        return !handle.done();
    }
};
// 使用这个生成器的协程函数
Generator range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i; // 这会调用 promise.yield_value(i)
    }
    // 协程结束,调用 promise.return_void()
}
int main() {
    auto gen = range(1, 5); // 创建协程,initial_suspend挂起,此时协程未执⾏
    while (gen.next()) { // 恢复协程,协程执⾏到co_yield处挂起
        std::cout << gen.value() << " "; // 从promise中获取产出的值
    }
    // 输出: 1 2 3 4
}

逻辑拆解:

  1. 协程创建阶段(触发: auto gen = range(1,5) ):调用 range 协程函数时,编译器自动创建协程帧(存储局部变量 i 、参数 start=1/end=5 、 promise_type 实例),并通过 promise 衔接 Generator。

  2. 初始挂起状态(触发: initial_suspend() ):协程创建后不立即执行,而是先挂起( suspend_always 表示 "总是挂起"),实现惰性执行(按需启动)。

  3. 恢复执行阶段(触发: gen.next() ):外部通过 Generator 的 next() 接口,用 handle 唤醒协程,执行到下⼀个 co_yield 或协程结束。

  4. 产出值挂起状态(触发: co_yield ): co_yield 是协程 "产出值" 的关键字,本质是调用promise.yield_value() ,并挂起协程,让外部获取产出值。

  5. 协程结束状态(触发: for 循环退出):range 的 for 循环执行完毕( i=5 >= end=5 ),协程无更多代码可执行,进入结束流程。

  6. 最终挂起状态(触发: final_suspend() ):协程结束后,通过 final_suspend() 挂起,等待外部手动销毁(避免协程帧提前释放)。

  7. 协程销毁阶段(触发: gen 析构) :main 函数结束, Generator 对象 gen 出作用域,析构函数释放协程帧内存。

下面是支持迭代器的协程返回对象:

复制代码
#include <coroutine>
#include <iostream>
template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        Generator get_return_object() {
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        std::suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        void return_void() {}
    };
    struct iterator {
        std::coroutine_handle<promise_type> handle;
        iterator(std::coroutine_handle<promise_type> h = nullptr) : handle(h)
        {}
        iterator& operator++() {
            if (handle && !handle.done()) {
                handle.resume();
            }
            return *this;
        }
        T operator*() const {
            return handle.promise().current_value;
        }
        bool operator!=(const iterator& other) const {
            return !handle.done();
        }
    };
    explicit Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }
    iterator begin() {
        if (handle)
        handle.resume();
        return iterator{ handle };
    }
    iterator end() { return iterator{}; }
private:
    std::coroutine_handle<promise_type> handle;
};
// 使用示例
Generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i; // 产生值并挂起
    }
}
Generator<int> fibonacci(int limit) {
    int a = 0, b = 1;
    while (a <= limit) {
        co_yield a;
        int next = a + b;
        a = b;
        b = next;
    }
}
int main() {
    std::cout << "Range 1-5: ";
    for (auto i : range(1, 6)) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    std::cout << "Fibonacci below 50: ";
    for (auto num : fibonacci(50)) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

样例二:异步线程恢复

复制代码
#include <iostream>
#include <coroutine>
#include <thread>
#include <chrono>
// 增强的可等待对象,添加更多日志
struct AsyncOperation {
    int value;
    bool ready = false;
    const char* name;
    AsyncOperation(int v, bool r, const char* n = "")
    : value(v), ready(r), name(n) {
        std::cout << "[" << name << "] AsyncOperation 构造: value=" << value<<", ready=" << ready << " (线程: " << std::this_thread::get_id() << ")" <<std::endl;
    }
    bool await_ready() const noexcept {
        std::cout << "[" << name << "] await_ready() 调用, 返回: " << ready<< "(线程: " << std::this_thread::get_id() << ")" << std::endl;
        return ready;
    }
    void await_suspend(std::coroutine_handle<> handle) noexcept {
        std::cout << "[" << name << "] await_suspend() 调用, 协程句柄: " <<handle.address()<< " (线程: " << std::this_thread::get_id() << ")" << std::endl;
        // 启动异步操作
        std::thread([this, handle, name = this->name]() {
            std::cout << "[" << name << "] 异步线程开始 (线程: "<<std::this_thread::get_id() << ")" << std::endl;
            std::this_thread::sleep_for(std::chrono::seconds(1));
            this->ready = true;
            std::cout << "[" << name << "] 异步操作完成, 准备恢复协程" << " (线程:" << std::this_thread::get_id() << ")"<< std::endl;
            // 恢复协程执行
            handle.resume();
            std::cout << "[" << name << "] 异步线程结束" << " (线程: " <<
            std::this_thread::get_id() << ")" << std::endl;
        }).detach();
        std::cout << "[" << name << "] await_suspend() 返回, 协程即将挂起" << "(线程: " << std::this_thread::get_id() << ")" << std::endl;
    }
    int await_resume() noexcept {
        std::cout << "[" << name << "] await_resume() 调用, 返回结果: " <<value<< " (线程: " << std::this_thread::get_id() << ")" << std::endl;
        return value;
    }
};
struct Task {
    struct promise_type {
        int current_value;
        Task get_return_object() {
            std::cout << "promise_type::get_return_object() 调用" << std::endl;
            return Task{std::coroutine_handle<promise_type>::from_promise(*this) };
        }
        std::suspend_never initial_suspend() noexcept {
            std::cout << "promise_type::initial_suspend() 调用 - 立即开始执行"<< std::endl;
            return {};
        }
        std::suspend_always final_suspend() noexcept {
            std::cout << "promise_type::final_suspend() 调用 - 协程结束" <<std::endl;
            return {};
        }
        void unhandled_exception() {
            std::cout << "promise_type::unhandled_exception() 调用" <<std::endl;
        }
        void return_void() {
            std::cout << "promise_type::return_void() 调用 - 协程正常返回" <<std::endl;
        }
    };
    std::coroutine_handle<promise_type> handle;
    Task(std::coroutine_handle<promise_type> h) : handle(h) {
        std::cout << "Task 对象构造, 句柄: " << handle.address() << std::endl;
    }
    ~Task() {
        if (handle) {
            std::cout << "Task 析构, 销毁协程句柄" << std::endl;
            handle.destroy();
        }
    }
};

Task detailed_async_example() {
    std::cout << "=== 协程函数开始执行 ===" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    std::cout << "\n--- 第一次 co_await ---" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    int result1 = co_await AsyncOperation{ 100, false, "操作1" };
    std::cout << "第一次 co_await 后继续执行, 结果: " << result1 << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    std::cout << "\n--- 第二次 co_await ---" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    int result2 = co_await AsyncOperation{ 200, false, "操作2" };
    std::cout << "第二次 co_await 后继续执行, 结果: " << result2 << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    std::cout << "\n--- 第三次 co_await (就绪状态) ---" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    int result3 = co_await AsyncOperation{ 300, true, "操作3" };
    std::cout << "第三次 co_await 后继续执行, 结果: " << result3 << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    std::cout << "\n=== 协程函数执行完成 ===" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
}
int main() {
    std::cout << "主函数开始 (线程: " << std::this_thread::get_id() << ")" <<std::endl;
    {
        std::cout << "\n********** 创建协程任务 **********" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
        auto task = detailed_async_example();
        std::cout << "协程任务已创建,控制权返回主函数" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
        // 给异步操作时间完成
        std::cout << "\n主线程等待 3 秒..." << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        std::cout << "主线程等待结束" << " (线程: " <<std::this_thread::get_id() << ")" << std::endl;
    } // task 析构
    std::cout << "\n主函数结束" << " (线程: " << std::this_thread::get_id() <<")" << std::endl;
    return 0;
}

样例三:echo服务器

复制代码
#include <iostream>
#include <coroutine>
#include <thread>
#include <vector>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <memory>
#include <array>
// 将文件描述符设置为非阻塞模式
static void set_nonblocking(int fd) {
    int flags = fcntl(fd, F_GETFL, 0); // 获取当前文件状态标志
    if (flags == -1) {
        perror("fcntl F_GETFL");
        return;
    }
    // 添加非阻塞标志
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl F_SETFL O_NONBLOCK");
    }
}

// I/O 等待器:用于协程等待 epoll 事件
struct IoAwaiter {
    int fd; // 要等待的文件描述符
    int events; // 要等待的事件类型:EPOLLIN(可读) 或 EPOLLOUT(可写)
    epoll_event ev{}; // epoll 事件结构
    // 静态成员,保存 epoll 实例的文件描述符
    static inline int epfd = -1;
    // 协程等待开始前调用
    // 返回 false 表示需要挂起等待
    bool await_ready() const noexcept {
        return false;
    }
    // 挂起协程,并将事件注册到 epoll
    void await_suspend(std::coroutine_handle<> h) noexcept {
        // 将协程句柄存到 epoll_event 的 data.ptr 中
        ev.data.ptr = h.address();
        // 设置事件类型,并使用边缘触发(EPOLLET)
        ev.events = events | EPOLLET;
        // 将 fd 和事件注册到 epoll
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
        }
        // 协程恢复后调用
        // 从 epoll 中移除该事件
    void await_resume() noexcept {
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
    }
};

// 等待 accept 事件的协程等待器
IoAwaiter co_accept(int fd) {
    return IoAwaiter{fd, EPOLLIN};
}

// 等待读事件的协程等待器
IoAwaiter co_read(int fd) {
    return IoAwaiter{fd, EPOLLIN};
}

// 等待写事件的协程等待器
IoAwaiter co_write(int fd) {
    return IoAwaiter{fd, EPOLLOUT};
}
// 协程任务类型
struct Task {
    struct promise_type {
        // 返回协程对象
        Task get_return_object() { return {}; }
        // 初始不挂起
        std::suspend_never initial_suspend() noexcept { return {}; }
        // 结束不挂起
        std::suspend_never final_suspend() noexcept { return {}; }
        // 协程返回 void
        void return_void() {}
        // 异常处理
        void unhandled_exception() { std::terminate(); }
    };
};

// 每个客户端连接的处理协程
Task echo_session(int sock) {
    try {
        std::array<char, 4096> buf; // 缓冲区
        for (;;) {
            // 等待 socket 可读
            co_await co_read(sock);
            // 读取数据
            ssize_t n = read(sock, buf.data(), buf.size());
            if (n <= 0) {
                // 连接关闭或出错
                if (n < 0) perror("read");
                break;
            }
            // 等待 socket 可写
            co_await co_write(sock);
            // 回写数据
            write(sock, buf.data(), n);
        }
    } catch (...) {
        std::cerr << "Session error\n";
    }
    close(sock); // 关闭连接
}

// 监听协程:接受新连接并创建会话协程
Task listener(int port) {
    // 创建监听 socket
    int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    if (s < 0) { perror("socket"); co_return; }
    // 设置 socket 选项:允许地址重用
    int opt = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    // 绑定地址和端口
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
    addr.sin_port = htons(port); // 端口号
    if (bind(s, (sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind");
        co_return;
    }
    // 开始监听
    if (listen(s, SOMAXCONN) < 0) {
        perror("listen");
        co_return;
    }
    std::cout << "Listening on port " << port << std::endl;
    // 循环接受新连接
    for (;;) {
        // 等待监听 socket 可读(有新连接)
        co_await co_accept(s);
        // 接受连接
        sockaddr_in peer{};
        socklen_t len = sizeof(peer);
        int client = accept4(s, (sockaddr*)&peer, &len, SOCK_NONBLOCK);
        if (client < 0) {
            if (errno != EAGAIN && errno != EWOULDBLOCK) {
                perror("accept");
            }
            continue;
        }
        std::cout << "New connection from " << inet_ntoa(peer.sin_addr) <<
        std::endl;
        // 为新连接创建会话协程
        echo_session(client);
    }
}
int main() {
    // 创建 epoll 实例
    IoAwaiter::epfd = epoll_create1(0);
    if (IoAwaiter::epfd < 0) {
        perror("epoll_create1");
        return 1;
    }
    // 启动监听协程
    listener(9000);
    // 事件循环
    std::vector<epoll_event> events(64); // 事件数组
    for (;;) {
        // 等待事件发生
        int nfds = epoll_wait(IoAwaiter::epfd, events.data(), events.size(),
        -1);
        if (nfds < 0) {
            perror("epoll_wait");
            break;
        }
        // 处理每个就绪事件
        for (int i = 0; i < nfds; ++i) {
            // 从事件中取出协程句柄并恢复执行
            auto h = std::coroutine_handle<>::from_address(events[i].data.ptr);
            h.resume();
        }
    }
}

面试题:

C++20 协程有什么缺陷?为什么 C++23 引入了协程栈大小优化?

C++20 协程的栈是无栈(stackless)的,意味着协程只能挂起在最后一个co_await/co_yield处,不支持递归协程(递归调用自身会导致栈溢出或行为未定义)。C++23 引入了有栈协程(stackful),支持更灵活的挂起和恢复,解决了递归等场景。

协程和线程的区别?

线程开销大,拥有 TCB 控制块、独立栈、TID 等内核级结构,是内核调度的最小单位。协程是用户态的轻量级执行单元,调度开销极小,由程序自身控制。仅在用户态通过堆上的 "协程帧" 保存状态,所有执行都复用线程的内核结构,完全依赖线程存在。千级 / 万级协程并发是可行的,而线程则不行。协程通常运行在线程池中,利用线程来执行。

协程的内存管理需要注意什么?

协程帧分配在堆上,必须确保其生命周期正确管理。忘记co_await一个协程任务,可能导致协程帧在任务完成前就被销毁,造成未定义行为。使用 RAII自动管理句柄销毁是良好实践。

说说std::suspend_always和std::suspend_never的区别和用途

  • std::suspend_never: await_ready() 返回True,协程永不挂起,立即执行。常用于initial_suspend()和final_suspend(),表示协程一开始就运行,或结束时立即清理。
  • std::suspend_always: await_ready() 返回false,协程总是挂起。需要手动调用 resume() 才能继续。

协程的应用场景

  • 异步 IO:传统异步 IO 依赖回调函数,代码嵌套深、可读性差、错误处理复杂;用线程池 + 同步 IO 则线程开销大(线程阻塞等待 IO 时,内核态切换 + 栈内存占用)。协程用co_await替代回调,代码逻辑和同步 IO 一致,但协程挂起时无内核态切换(用户态暂停),且协程栈(堆分配)仅几十字节,远小于线程栈(MB 级)。
  • 耗时任务的 "轻量级并发":CPU 密集型 + IO 密集型混合任务(如 "请求接口→解析数据→计算结果"),用线程池会因线程数过多导致调度开销大;用单线程则串行执行效率低。协程可在单线程内并发执行多个任务(IO 等待时挂起,CPU 计算时恢复),既利用 CPU,又不浪费线程资源。

协程的不适用场景

  • 纯 CPU 密集型任务:协程的优势是 "挂起 / 恢复",但纯 CPU 任务(如矩阵运算、加密解密)无挂起点,协程无法发挥作用,且协程的堆分配 / 状态机转换会带来微小开销,不如直接用线程池 + 多线程并行(充分利用多核)。
  • 简单的短任务:如 "计算两数之和""字符串拼接",协程的初始化(堆分配协程帧)开销远大于任务本身,不如普通函数高效。
  • 需要实时性的场景:协程的调度依赖用户态逻辑(无内核抢占),若一个协程长期占用 CPU,会阻塞同线程的其他协程,无法满足硬实时(如工业控制、车载系统)要求,不如线程(内核抢占调度)可靠。
相关推荐
wangjialelele1 天前
C++11、C++14、C++17、C++20新特性解析(一)
linux·c语言·开发语言·c++·c++20·visual studio
telllong2 天前
C++20 Modules:从入门到真香
java·前端·c++20
Max_uuc1 个月前
【架构心法】逃离回调地狱:从 Protothreads 到 C++20 协程 (Coroutines) 的嵌入式进化
c++20
阿猿收手吧!1 个月前
【C++】C++20协程的await_transform和coroutine_handle
开发语言·c++·c++20
阿猿收手吧!1 个月前
【C++】 co_yield如何成为语法糖?解析其背后的Awaitable展开与协程状态跃迁
c++·c++20
吐泡泡_1 个月前
C++20(三路比较运算符)
c++20
啟明起鸣2 个月前
【C++20新特性】概念约束特性与 “模板线程池”,概念约束是为了 “把握未知对象”
开发语言·c++·c++20·模板线程池
linweidong2 个月前
虎牙C++面试题及参考答案(上)
stl·vector·线程·内存管理·c++20·c++面试·c++调用
吐泡泡_2 个月前
C++20(概念和约束)
c++20