std::thread因空原生线程触发段错误问题排查求助
段错误原因分析:线程启动与对象初始化的竞态问题
问题概述
使用-O3优化参数编译代码并配合ThreadSanitizer检测时,触发零页地址写入的段错误;代码直接运行时无异常,仅在线程运行场景下出现问题。
重现代码
#include <fstream> #include <sstream> #include <thread> #include <iostream> #include <algorithm> #include <new> #include <thread> #include <string> class temp { private: std::thread m_thread; std::vector<std::string> m_tokens; public: temp() { m_thread = std::thread(&temp::run, this); } void run() { while (1) { std::string token; std::cin >> token; if (std::ranges::find(m_tokens, token) != m_tokens.end()) { std::cout << "duplicate token" << token << "\n"; continue; } m_tokens.push_back(token); std::cout << token << " added\n"; } } }; int main(int argc, char **argv) { temp t; while (1) ; return EXIT_SUCCESS; }
错误日志
ThreadSanitizer:DEADLYSIGNAL ==19246==ERROR: ThreadSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7f81d5f98880 bp 0x7b0800000020 sp 0x7f81d30bf298 T19247) ==19246==The signal is caused by a WRITE memory access. ==19246==Hint: address points to the zero page. #0 __tsan_func_entry <null> (libtsan.so.0+0x9e880) #1 std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (temp::*)(), temp*>>>::_M_run() /opt/rh/devtoolset-11/root/usr/include/c++/11/bits/std_thread.h:211 (trading+0x41e3a3) #2 execute_native_thread_routine <null> (trading+0x437ab3) #3 start_thread <null> (libpthread.so.0+0x7ea4) #4 clone <null> (libc.so.6+0xfeb0c) ThreadSanitizer can not provide additional info. SUMMARY: ThreadSanitizer: SEGV (/lib64/libtsan.so.0+0x9e880) in __tsan_func_entry ==19246==ABORTING
核心原因
对象初始化顺序与线程启动的竞态
C++类成员的初始化顺序严格按照类中声明的顺序执行,而非构造函数中的赋值顺序。在temp类中,m_thread先于m_tokens声明,因此构造函数执行时:- 先完成
m_thread的默认初始化 - 接着执行
m_thread = std::thread(&temp::run, this)启动线程 - 最后才初始化
m_tokens(std::vector的构造)
线程启动后可能立即执行run方法,此时m_tokens尚未完成构造,访问这个未初始化的成员属于未定义行为,在-O3优化下,编译器的指令重排会加剧这种竞态,导致ThreadSanitizer检测到零页访问错误。
- 先完成
ThreadSanitizer的检测放大
直接运行时未触发错误是因为未定义行为的表现具有随机性,但ThreadSanitizer会对内存访问做严格检测,将这种潜在的非法访问暴露为明确的段错误。
解决方案
- 延迟线程启动:不在构造函数中启动线程,新增一个单独的
start方法,在对象完全构造完成后调用:class temp { private: std::thread m_thread; std::vector<std::string> m_tokens; bool m_running = false; public: temp() = default; void start() { if (!m_running) { m_thread = std::thread(&temp::run, this); m_running = true; } } void run() { while (1) { std::string token; std::cin >> token; if (std::ranges::find(m_tokens, token) != m_tokens.end()) { std::cout << "duplicate token" << token << "\n"; continue; } m_tokens.push_back(token); std::cout << token << " added\n"; } } }; int main() { temp t; t.start(); // 对象构造完成后启动线程 while(1); return 0; } - 调整成员声明顺序:如果一定要在构造函数中启动线程,将
m_tokens的声明放在m_thread之前,确保m_tokens先完成初始化,再启动线程:class temp { private: std::vector<std::string> m_tokens; // 先声明,先初始化 std::thread m_thread; public: temp() { m_thread = std::thread(&temp::run, this); } void run() { while (1) { std::string token; std::cin >> token; if (std::ranges::find(m_tokens, token) != m_tokens.end()) { std::cout << "duplicate token" << token << "\n"; continue; } m_tokens.push_back(token); std::cout << token << " added\n"; } } }; - 添加同步机制:即使解决了初始化问题,
run方法中对m_tokens的读写仍属于多线程无同步访问,会导致数据竞争,需添加std::mutex保护:class temp { private: std::thread m_thread; std::vector<std::string> m_tokens; std::mutex m_mutex; public: temp() = default; void start() { m_thread = std::thread(&temp::run, this); } void run() { while (1) { std::string token; std::cin >> token; std::lock_guard<std::mutex> lock(m_mutex); if (std::ranges::find(m_tokens, token) != m_tokens.end()) { std::cout << "duplicate token" << token << "\n"; continue; } m_tokens.push_back(token); std::cout << token << " added\n"; } } };
内容的提问来源于stack exchange,提问作者avatli
相关产品推荐
相关产品推荐

