boost::filesystem是否线程安全?多线程数据竞态问题排查
Boost.Filesystem 1.74 多线程下的数据竞争问题分析
问题现象
在Debian稳定版系统中运行多线程程序时,Valgrind DRD检测到冲突加载:
% valgrind --tool=drd ./threads ==1368067== drd, a thread error detector ==1368067== Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche. ==1368067== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info ==1368067== Command: ./threads ==1368067== ==1368067== Thread 3: ==1368067== Conflicting load by thread 3 at 0x048cc208 size 8 ==1368067== at 0x48C1CB6: size (basic_string.h:1064) ==1368067== by 0x48C1CB6: boost::filesystem::path::end() const (path.cpp:737) ==1368067== by 0x48C2331: boost::filesystem::path::compare(boost::filesystem::path const&) const (path.cpp:178) ==1368067== by 0x48C26D9: operator== (path.hpp:771) ==1368067== by 0x48C26D9: boost::filesystem::path::extension() const (path.cpp:353) ==1368067== by 0x48C284E: boost::filesystem::path::replace_extension(boost::filesystem::path const&) (path.cpp:238) ==1368067== by 0x10B449: run(void*) (in /home/mathieu/Perso/PublicRep/cxx/boost/bin/threads) ==1368067== by 0x484CD34: ??? (in /usr/libexec/valgrind/vgpreload_drd-amd64-linux.so) ==1368067== by 0x4B90133: start_thread (pthread_create.c:442) ==1368067== by 0x4C0FA3F: clone (clone.S:100) ==1368067== Allocation context: BSS section of /usr/lib/x86_64-linux-gnu/libboost_filesystem.so.1.74.0 ==1368067== Other segment start (thread 2) ==1368067== (thread finished, call stack no longer available) ==1368067== Other segment end (thread 2) ==1368067== (thread finished, call stack no longer available) ==1368067==
复现代码
// threads.cxx #if 0 #include <filesystem> namespace fs = std::filesystem; #else #include <boost/filesystem/path.hpp> namespace fs = boost::filesystem; #endif #include <pthread.h> #define N 32 static void* run(void* arg) { uintptr_t job = (uintptr_t)arg; fs::path path{"/foo/bar.jpg"}; path.replace_extension("png"); return nullptr; } int main(int argc, char* argv[]) { uintptr_t i; int rc; pthread_t threads[N]; for (i = 0; i < N; ++i) { if ((rc = pthread_create(threads + i, nullptr, run, (void*)i))) { return rc; } } for (i = 0; i < N; ++i) { if ((rc = pthread_join(threads[i], nullptr))) { return rc; } } return 0; }
使用std::filesystem时未复现该问题,仅Boost版本触发检测。
环境信息
- 系统:Debian稳定版
- 编译器:g++ 12.2.0-14
- Valgrind:3.19.0
更新:Helgrind检测结果
% make && valgrind --tool=helgrind ./threads [100%] Built target threads ==1455067== Helgrind, a thread error detector ==1455067== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al. ==1455067== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info ==1455067== Command: ./threads ==1455067== ==1455067== ---Thread-Announcement------------------------------------------ ==1455067== ==1455067== Thread #3 was created ==1455067== at 0x4BEEA2F: clone (clone.S:76) ==1455067== by 0x4BEF878: __clone_internal (clone-internal.c:83) ==1455067== by 0x4B6ED6F: create_thread (pthread_create.c:295) ==1455067== by 0x4B6F82C: pthread_create@@GLIBC_2.34 (pthread_create.c:831) ==1455067== by 0x484B5D7: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so) ==1455067== by 0x10B2AD: main (in /home/mathieu/Perso/PublicRep/cxx/boost/bin/threads) ==1455067== ==1455067== ---Thread-Announcement------------------------------------------ ==1455067== ==1455067== Thread #2 was created ==1455067== at 0x4BEEA2F: clone (clone.S:76) ==1455067== by 0x4BEF878: __clone_internal (clone-internal.c:83) ==1455067== by 0x4B6ED6F: create_thread (pthread_create.c:295) ==1455067== by 0x4B6F82C: pthread_create@@GLIBC_2.34 (pthread_create.c:831) ==1455067== by 0x484B5D7: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so) ==1455067== by 0x10B2AD: main (in /home/mathieu/Perso/PublicRep/cxx/boost/bin/threads) ==1455067== ==1455067== ---------------------------------------------------------------- ==1455067== ==1455067== Possible data race during read of size 8 at 0x48AB208 by thread #3 ==1455067== Locks held: none ==1455067== at 0x48A0CB6: size (basic_string.h:1064) ==1455067== by 0x48A0CB6: boost::filesystem::path::end() const (path.cpp:737) ==1455067== by 0x48A1331: boost::filesystem::path::compare(boost::filesystem::path const&) const (path.cpp:178) ==1455067== by 0x48A16D9: operator== (path.hpp:771) ==1455067== by 0x48A16D9: boost::filesystem::path::extension() const (path.cpp:353) ==1455067== by 0x48A184E: boost::filesystem::path::replace_extension(boost::filesystem::path const&) (path.cpp:238) ==1455067== by 0x10B449: run(void*) (in /home/mathieu/Perso/PublicRep/cxx/boost/bin/threads) ==1455067== by 0x484B7D6: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so) ==1455067== by 0x4B6F133: start_thread (pthread_create.c:442) ==1455067== by 0x4BEEA3F: clone (clone.S:100) ==1455067== ==1455067== This conflicts with a previous write of size 8 by thread #2 ==1455067== Locks held: none ==1455067== at 0x48A03B0: _M_length (basic_string.h:229) ==1455067== by 0x48A03B0: _M_set_length (basic_string.h:267) ==1455067== by 0x48A03B0: _M_construct<char const*> (basic_string.tcc:247) ==1455067== by 0x48A03B0: basic_string<> (basic_string.h:642) ==1455067== by 0x48A03B0: path (path.hpp:171) ==1455067== by 0x48A03B0: boost::filesystem::detail::dot_path() (path.cpp:698) ==1455067== by 0x48A16CE: boost::filesystem::path::extension() const (path.cpp:353) ==1455067== by 0x48A184E: boost::filesystem::path::replace_extension(boost::filesystem::path const&) (path.cpp:238) ==1455067== by 0x10B449: run(void*) (in /home/mathieu/Perso/PublicRep/cxx/boost/bin/threads) ==1455067== by 0x484B7D6: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so) ==1455067== by 0x4B6F133: start_thread (pthread_create.c:442) ==1455067== by 0x4BEEA3F: clone (clone.S:100) ==1455067== Address 0x48ab208 is 8 bytes inside data symbol "_ZZN5boost10filesystem6detail8dot_pathEvE7dot_pth" ==1455067== ==1455067== ----------------------------------------------------------------
更新2:核心代码分析
查看boost::filesystem::detail::dot_path()实现:
const path& dot_path() { #ifdef BOOST_WINDOWS_API static const fs::path dot_pth(L"."); #else static const fs::path dot_pth("."); #endif return dot_pth; }
问题根源
这段代码存在线程安全问题:
- C11及以后标准规定,函数内的静态变量初始化是线程安全的(初始化完成前会自动加锁),但Boost 1.74的构建可能未启用C11或更高标准的线程安全初始化机制,或者其依赖的编译器/标准库未完全支持该特性。
- 从Valgrind检测结果来看,线程2正在初始化静态变量
dot_pth(写入其内部字符串的长度),而线程3同时读取该变量的长度,触发了数据竞争。
对比std::filesystem
std::filesystem未出现问题,是因为标准库实现严格遵循C++11+标准,保证了函数内静态变量的线程安全初始化,避免了多线程竞争。
解决方案
- 升级Boost版本:较新的Boost.Filesystem版本已修复该问题,确保静态变量初始化的线程安全性。
- 手动初始化:在程序启动的单线程阶段提前调用
dot_path(),完成静态变量的初始化,避免多线程竞争。 - 编译选项调整:确保编译时启用C++11或更高标准(如
-std=c++11),并使用支持线程安全静态初始化的编译器版本。
内容的提问来源于stack exchange,提问作者malat
相关产品推荐
相关产品推荐

