为何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()才能释放,你的代码完全没有触发这个销毁动作。
具体执行流程分析
- 每次调用
second(loop)会创建一个新协程,执行到co_await sleep(1)时挂起,等待定时器回调恢复。 - 定时器触发后,协程恢复并执行完毕,进入
final_suspend()阶段,返回你定义的final_awaitable。 - 当前
final_awaitable::await_suspend仅负责恢复续体(即service_main协程),但没有销毁当前second协程的状态。协程的堆内存不会自动回收,必须显式调用destroy()才会触发promise的析构和内存释放(也就是你重载的operator delete)。 - 你的
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
相关产品推荐
相关产品推荐

