C++-coroutines协程 协程之间相互切换

C++协程切换的机制基于如下C++协程标准的规定:await_suspend如果直接返回一个coroutine_handle协程句柄。那么被返回的句柄会立即恢复,即调用返回coroutine_handle的resume()方法

查看如下例子:

#include 
#include 
#include 

// 前向声明
struct Task;

// 一个简单的 Awaiter,用于触发协程切换
struct SwitchTo {
    std::coroutine_handle<> target_coro;

    bool await_ready() const noexcept {
        return false;
    }

    std::coroutine_handle<> await_suspend(std::coroutine_handle<> /*current_coro*/) const noexcept {
        std::cout << "    [Awaiter] Suspending current coroutine, switching to target.\n";
        return target_coro;
    }

    void await_resume() const noexcept {
        std::cout << "    [Awaiter] Resumed original coroutine.\n";
    }
};

// 一个专门用于获取当前协程句柄的 Awaiter
struct GetCurrentHandle {
    bool await_ready() const noexcept { return false; }
    bool await_suspend(std::coroutine_handle<> h) noexcept {
        handle = h;
        return false; // 关键:返回 false,表示不挂起,立即恢复
    }
    std::coroutine_handle<> await_resume() const noexcept {
        return handle;
    }
private:
    std::coroutine_handle<> handle;
};


// 一个简单的协程 Task 类型
struct Task {
    struct promise_type {
        Task get_return_object() { return { std::coroutine_handle<promise_type>::from_promise(*this) }; }
        std::suspend_always initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };

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

// 前向声明
Task coro_B(std::coroutine_handle<> caller);

// 第一个协程
Task coro_A() {
    std::cout << "[Coro A] Started.\n";

    // 正确的方式:co_await 一个特制的 awaiter 来获取自己的句柄
    auto self_handle = co_await GetCurrentHandle{};

    // 创建 coro_B,并把自己的句柄传给它
    std::cout << "[Coro A] Creating Coro B.\n";
    Task b = coro_B(self_handle);

    std::cout << "[Coro A] About to switch to Coro B.\n";
    co_await SwitchTo{ b.handle }; // <--- 切换到 coro_B

    std::cout << "[Coro A] Back from Coro B.\n";
    std::cout << "[Coro A] Finished.\n";
}

// 第二个协程
Task coro_B(std::coroutine_handle<> caller_handle) {
    std::cout << "  [Coro B] Started.\n";
    std::cout << "  [Coro B] About to switch back to Coro A.\n";

    co_await SwitchTo{ caller_handle }; // <--- 切换回调用者 (coro_A)

    std::cout << "  [Coro B] This line will not be printed because we never resume B.\n";
}


int main() {
    std::cout << "Main: Starting Coro A.\n";
    Task a = coro_A();

    std::cout << "Main: Resuming Coro A to run.\n";
    a.handle.resume(); // 启动 coro_A

    std::cout << "Main: Coro A handle is done: " << std::boolalpha << a.handle.done() << "\n";

    // Task 的析构函数会自动清理句柄

    std::cout << "Main: Finished.\n";
    return 0;
}

GetCurrentHandle是一个特别的awaiter,通过协程自动传递coroutine_handle给await_suspend的机制,我们得到了promise_type对应的coroutine_handle,并且通过await_suspend return false继续执行await_resume的机制,我们直接可以将handle返回给co_await表达式。

执行另一个协程函数,其挂起时我们可以得到外层类对象Task 。理论上说,如果Task的promise_type::initial_suspend返回的awaitable不挂起的话,我们直接就可以执行协程B了。这里是为了演示如何通过awaiter的await_suspend来实现协程的切换。

SwitchTo 类也是一个awaiter,它包含了一个目标协程句柄的成员变量,在co_await时,通过传入一个协程句柄初始化该变量,并且await_ready总是返回false,此时进入await_suspend,返回刚刚初始化的句柄,于是根据C++协程标准规定,会继续执行目标句柄对应的协程,即调用该句柄的resume方法,此时目标句柄继续执行,当前句柄(即await_suspend参数传进来的句柄)暂停执行。

由于我们在协程B传入了协程A的句柄,所以在适当的时候,我们可以通过同样的方法来切换为协程A的执行。

最后,我们要尤其注意内存泄露的问题,这里我们通过RAII的方式管理内存。当协程A执行完毕,Task b变量析构,其handle被释放,避免了内存泄漏,此时协程b的handle.done()返回false,即没有执行完成,但也需要释放,否则后面没有机会释放了。
而在main函数中,变量Task a离开作用域时,也通过析构函数释放handle,整个程序没有内存泄漏。

你可能感兴趣的:(C++,#,Coroutines,c++,服务器,算法,协程,coroutines)