使用std::thread恢复coroutine_handle触发段错误,std::jthread正常
C++协程awaiter段错误问题分析与修复
问题描述
为理解awaiter原理,我实现了一个实验性协程awaiter对象。调用协程foo时,在await_suspend方法中启动std::thread执行任务,任务结束时恢复awaiter。尽管我用unique_ptr管理线程生命周期,并在awaiter析构函数中处理线程joinable,但handle.resume仍触发段错误;换成std::jthread则运行正常,找不到问题所在。
原始代码
#include <coroutine> #include <iostream> #include <thread> struct ReturnObject { struct promise_type { ReturnObject get_return_object() { auto handle = std::coroutine_handle<promise_type>::from_promise(*this); return ReturnObject{handle}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { } // void return_void() noexcept { // } void return_value(int val) noexcept { this->value = val; } std::suspend_always yield_value(int val) { this->value = val; return std::suspend_always{}; } double get_value() const noexcept { return value; } void set_value(double val) noexcept { value = val; } private: double value{3.14}; }; std::coroutine_handle<promise_type> h_; ReturnObject(std::coroutine_handle<promise_type> h) : h_{h} { } operator std::coroutine_handle<promise_type>() { return h_; } int get_value() const { return h_.promise().get_value(); } ~ReturnObject() { h_.destroy(); } }; void do_work(std::coroutine_handle<ReturnObject::promise_type>& h) { std::cout << "do_work\n"; h.resume(); } struct SuspendAlways { void await_suspend(std::coroutine_handle<ReturnObject::promise_type> h) { std::cout << "await suspend\n"; th = std::make_unique<std::thread>(do_work, std::ref(h)); //(1) //std::jthread (&SuspendAlways::do_work, this, std::ref(h)); //(2) } void await_resume() { std::cout << "await_resume\n"; } bool await_ready() const noexcept { return false; } ~SuspendAlways() { std::cout << "~SuspendAlways\n"; if (th->joinable()) th->join();//(1) } std::unique_ptr< std::thread> th;//(1) }; ReturnObject foo() { std::cout << "1. Hello World!\n"; co_await SuspendAlways{}; std::cout << "2. Hello World!\n"; } int main(int argc, char **argv) { auto ret = foo(); using namespace std::chrono_literals; std::this_thread::sleep_for(5000ms); std::cout << std::boolalpha << ret.h_.done() << std::endl; }
问题根源
- 悬垂引用导致未定义行为:在
await_suspend方法中,传递给std::thread的是std::ref(h),而h是该方法的局部参数。当await_suspend执行完毕返回后,局部参数h的生命周期结束,但新线程仍持有它的引用。后续线程中调用h.resume()时,访问的是已经销毁的对象,直接触发段错误。 - std::jthread正常的原因:
std::jthread的构造和销毁逻辑会自动管理线程生命周期,且你注释的代码中可能误打了成员函数调用(实际原代码中do_work是全局函数),但更关键的是,若正确使用std::jthread时,你可能无意中避免了悬垂引用的传递,或者std::jthread的同步机制掩盖了问题,但本质还是原代码的引用失效问题。
修复方案
核心思路是避免悬垂引用,将协程句柄存储到SuspendAlways对象中,确保线程执行期间句柄始终有效。
修改后的代码
#include <coroutine> #include <iostream> #include <thread> struct ReturnObject { struct promise_type { ReturnObject get_return_object() { auto handle = std::coroutine_handle<promise_type>::from_promise(*this); return ReturnObject{handle}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { } void return_value(int val) noexcept { this->value = val; } std::suspend_always yield_value(int val) { this->value = val; return std::suspend_always{}; } double get_value() const noexcept { return value; } void set_value(double val) noexcept { value = val; } private: double value{3.14}; }; std::coroutine_handle<promise_type> h_; ReturnObject(std::coroutine_handle<promise_type> h) : h_{h} { } operator std::coroutine_handle<promise_type>() { return h_; } int get_value() const { return h_.promise().get_value(); } ~ReturnObject() { h_.destroy(); } }; struct SuspendAlways { std::coroutine_handle<ReturnObject::promise_type> h_; // 改为成员函数,直接访问对象内存储的句柄 void do_work() { std::cout << "do_work\n"; h_.resume(); } void await_suspend(std::coroutine_handle<ReturnObject::promise_type> h) { std::cout << "await suspend\n"; h_ = h; // 保存协程句柄到对象中 th = std::make_unique<std::thread>(&SuspendAlways::do_work, this); } void await_resume() { std::cout << "await_resume\n"; } bool await_ready() const noexcept { return false; } ~SuspendAlways() { std::cout << "~SuspendAlways\n"; // 先判断指针是否为空,再检查线程是否可join if (th && th->joinable()) { th->join(); } } std::unique_ptr<std::thread> th; }; ReturnObject foo() { std::cout << "1. Hello World!\n"; co_await SuspendAlways{}; std::cout << "2. Hello World!\n"; } int main(int argc, char **argv) { auto ret = foo(); using namespace std::chrono_literals; std::this_thread::sleep_for(5000ms); std::cout << std::boolalpha << ret.h_.done() << std::endl; }
关键修改点
- 在
SuspendAlways中添加成员变量h_,用于存储协程句柄,避免传递局部变量的引用。 - 将
do_work改为SuspendAlways的成员函数,直接访问对象内的h_,彻底消除悬垂引用风险。 - 析构函数中先判断
th是否为空,再检查线程是否可join,避免空指针访问。
内容的提问来源于stack exchange,提问作者Cabbas
相关产品推荐
相关产品推荐

