std::jthread内部获取std::stop_token为何存在竞态条件?
std::jthread内部获取stop_token的竞态条件根源解析
当我们在std::jthread的线程函数内部,通过捕获的jthread对象引用调用get_stop_token()时,会存在竞态条件。结合MSVC的实现代码,我们可以精准定位问题根源:
MSVC中std::jthread的构造函数实现
jthread() noexcept : _Impl{}, _Ssource{nostopstate} {} template <class _Fn, class... _Args, enable_if_t<!is_same_v<remove_cvref_t<_Fn>, jthread>, int> = 0> _NODISCARD_CTOR explicit jthread(_Fn&& _Fx, _Args&&... _Ax) { if constexpr (is_invocable_v<decay_t<_Fn>, stop_token, decay_t<_Args>...>) { _Impl._Start(_STD forward<_Fn>(_Fx), _Ssource.get_token(), _STD forward<_Args>(_Ax)...); } else { _Impl._Start(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); } }
从代码能看到,_Ssource(关联的停止状态源)确实在调用_Start启动新线程前就完成了构造,且调用_Start前的所有内存操作对未启动的新线程可见。
有问题的线程函数写法
auto t = std::jthread{ [&]() { auto token = t.get_stop_token(); // ...后续操作 } };
std::jthread::get_stop_token的实现
_NODISCARD stop_token get_stop_token() const noexcept { return _Ssource.get_token(); }
竞态条件的具体位置
竞态条件不是_Ssource在新线程启动后、原线程_Start返回前被修改,而是出现在新线程访问了尚未完全构造的jthread对象t:
在auto t = std::jthread{ [...] };这条语句中,jthread的构造函数会先启动新线程(通过_Impl._Start),但构造函数本身还没执行完毕——也就是说,t对象还没有完成全部初始化工作。此时新线程通过捕获的引用直接访问t并调用get_stop_token(),本质是在访问一个尚未完全构造完成的对象,这属于C++标准中的未定义行为,而线程启动时机和对象构造完成时机的不确定性,就构成了竞态条件。
内容的提问来源于stack exchange,提问作者Liviu Stancu
相关产品推荐
相关产品推荐

