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

无同步时跨线程co_await并恢复C++协程是否安全?

跨线程co_await恢复协程的线程安全性问题

cppreference提供了一个co_await示例,该示例会在另一个线程恢复协程。但在不进行同步的情况下,跨线程co_await并恢复协程是否安全?

具体来说,resuming_on_new_thread()在一个线程启动,co_await后在另一个线程恢复并访问同一个协程“帧”。通常从两个线程访问同一数据需要同步,示例代码如下:

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
 
auto switch_to_new_thread(std::jthread& out)
{
    struct awaitable
    {
        std::jthread* p_out;
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<> h)
        {
            std::jthread& out = *p_out;
            if (out.joinable())
                throw std::runtime_error("Output jthread parameter not empty");
            out = std::jthread([h] { h.resume(); });
            // 潜在未定义行为:访问可能已销毁的*this
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // 此操作安全
        }
        void await_resume() {}
    };
    return awaitable{&out};
}
 
struct task
{
    struct promise_type
    {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};
 
task resuming_on_new_thread(std::jthread& out)
{
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter在此处销毁
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
 
int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

进一步考虑函数包含数据的场景:

task resuming_on_new_thread(std::jthread& out)
{
    int data = 0; // 使用线程A存储一些数据。
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter在此处销毁
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
    std::cout << data << '\n'; // 使用线程B访问data。**这样安全吗?**
}

核心疑问

上述场景中,跨线程访问协程帧及其中的data,无需同步是否安全?


解答

这种场景下是安全的,不需要额外的同步机制,原因如下:

  1. 协程帧的访问时序保证
    协程挂起后,原线程(线程A)会立即退出协程的挂起点,直到协程被恢复前,线程A不会再对协程帧进行任何操作;新线程(线程B)只有通过h.resume()恢复协程后,才会开始访问协程帧。两者的访问是严格的先后关系,不存在并发访问的情况。

  2. co_await挂起的语义约束
    co_await的挂起过程具有明确的语义:当await_suspend执行完成并返回后,原线程与协程帧的关联就暂时断开,不会再触发对协程帧的读写。新线程的resume()操作是协程帧的唯一后续访问入口,此时协程帧的“使用权”已经完全转移到新线程,不存在数据竞争的可能。

  3. 局部变量的访问安全性
    示例中的data存储在协程帧中,线程A在co_await之前完成了对data的写入操作,之后线程A不会再访问该变量;线程B恢复协程后才开始读取data,属于单写单读的时序访问,符合C++内存模型的安全要求,不需要额外同步。

另外需要注意示例中注释的潜在未定义行为:await_suspend中直接访问p_out的问题,是因为awaitable对象可能在原线程挂起后被销毁,而非协程帧本身的线程安全问题。


内容的提问来源于stack exchange,提问作者trev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 15:01:11