You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何C++协程状态在循环执行时未被释放?

C++协程状态未释放问题排查

我正在学习C++协程的工作原理,但不清楚为何在循环执行过程中,协程状态始终未被释放。我认为second(loop)创建的promise在co_await表达式返回后就应立即销毁,因为它未被存储在任何地方,请问我忽略了什么?

#include <uv.h>

#include <iostream>
#include <experimental/coroutine>

class service {
public:
    struct awaiter;
    struct promise_type;

    using coro_handle = std::experimental::coroutine_handle<promise_type>;

    coro_handle _co;

    service(coro_handle co) : _co(co) {
    }

    service(const service &) = delete;
    service(service &&) = delete;

    awaiter operator co_await();
};

struct final_awaitable {
    std::experimental::coroutine_handle<> _co;

    final_awaitable(std::experimental::coroutine_handle<> co) : _co(co) {
    }

    bool await_ready() noexcept {
        return false;
    }

    std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<>) noexcept {
        if (_co) {
            return _co;
        } else {
            return std::experimental::noop_coroutine();
        }
    }

    void await_resume() noexcept {}
};

template<typename T>
concept UvRunnable = requires(T) {
  { &T::uv_run_task };
};

struct sleep {
    unsigned long _delay;

    explicit sleep(unsigned long delay) : _delay(delay) {
    }

    auto uv_run_task(uv_loop_t* loop, void* co) {
        uv_timer_t *timer = new uv_timer_t{};
        uv_timer_init(loop, timer);
        timer->data = co;
        uv_timer_start(timer, &on_sleep_done, _delay, 0);
        return std::experimental::suspend_always{};
    }

    static void on_sleep_done(uv_timer_t *timer) {
        auto co = std::experimental::coroutine_handle<>::from_address(timer->data);
        delete timer;
        co.resume();
    }
};

struct service::promise_type {
    uv_loop_t* _loop;
    std::experimental::coroutine_handle<> _continuation;

    promise_type(uv_loop_t *loop) : _loop(loop) {}

    auto get_return_object() {
        return service::coro_handle::from_promise(*this);
    }

    auto initial_suspend() {
        return std::experimental::suspend_never();
    }

    auto final_suspend() noexcept {
        return final_awaitable{ _continuation };
    }

    void return_void() {
    }

    void* operator new(std::size_t n) {
        std::cout << "Allocating " << n << " bytes" << std::endl;
        return ::operator new(n);
    }

    void operator delete(void* ptr, std::size_t size) {
        std::cout << "Freeing memory" << std::endl;
        ::operator delete(ptr);
    }

    template <typename T>
    auto && await_transform(T &&obj) const noexcept {
        return std::forward<T>(obj);
    }

    template <UvRunnable T>
    auto await_transform(T cmd) {
        return cmd.uv_run_task(_loop, coro_handle::from_promise(*this).address());
    }

    void unhandled_exception() {
        std::cout << "Unhandled exception" << std::endl;
        std::terminate();
    }
};

struct service::awaiter {
    service::coro_handle _co;

    awaiter(coro_handle co) : _co(co) {
    }

    bool await_ready() {
        return false;
    }

    void await_suspend(std::experimental::coroutine_handle<> co_cont) {
        _co.promise()._continuation = co_cont;
    }

    void await_resume() {
    }
};

service::awaiter service::operator co_await() {
    return service::awaiter(_co);
}

service second(uv_loop_t *loop) {
    co_await sleep(1);
}

service service_main(uv_loop_t *loop) {
    for (int i = 0; i < 3; i++) {
        std::cout << "#" << i << std::endl;
        co_await second(loop);
    }
}

int main() {
    uv_loop_t loop;
    uv_loop_init(&loop);

    std::cout << "before service_main" << std::endl;
    service_main(&loop);
    std::cout << "after service_main" << std::endl;
    int res = uv_run(&loop, UV_RUN_DEFAULT);

    return 0;
}

核心问题:协程状态不会自动销毁

你忽略了C++协程的堆分配状态(包括promise)需要显式调用coroutine_handle::destroy()才能释放,你的代码完全没有触发这个销毁动作。

具体执行流程分析

  1. 每次调用second(loop)会创建一个新协程,执行到co_await sleep(1)时挂起,等待定时器回调恢复。
  2. 定时器触发后,协程恢复并执行完毕,进入final_suspend()阶段,返回你定义的final_awaitable。
  3. 当前final_awaitable::await_suspend仅负责恢复续体(即service_main协程),但没有销毁当前second协程的状态。协程的堆内存不会自动回收,必须显式调用destroy()才会触发promise的析构和内存释放(也就是你重载的operator delete)。
  4. 你的service类持有协程句柄,但在co_await完成后没有任何逻辑去调用destroy(),导致每个second协程的promise内存持续泄漏。

修复方案

修改final_awaitable的await_suspend方法,在恢复续体前销毁当前协程的状态:

std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<> current) noexcept {
    // 销毁当前协程的状态,释放promise内存
    current.destroy();
    if (_co) {
        return _co;
    } else {
        return std::experimental::noop_coroutine();
    }
}

这样,当second协程执行到最终挂起时,会先销毁自身的状态,再恢复service_main协程,循环三次后就能看到三次"Freeing memory"的输出,对应三次second协程的内存释放。

补充说明

C++协程的默认分配策略是在堆上分配状态(除非你自定义promise_type的operator new/delete做栈分配或其他处理)。编译器不会自动管理这块内存的生命周期,开发者必须在合适的时机调用coroutine_handle::destroy()来完成清理,通常是在协程执行完毕且不再需要其状态时。

内容的提问来源于stack exchange,提问作者Maël Nison

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 02:36:03