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

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+标准,保证了函数内静态变量的线程安全初始化,避免了多线程竞争。

解决方案

  1. 升级Boost版本:较新的Boost.Filesystem版本已修复该问题,确保静态变量初始化的线程安全性。
  2. 手动初始化:在程序启动的单线程阶段提前调用dot_path(),完成静态变量的初始化,避免多线程竞争。
  3. 编译选项调整:确保编译时启用C++11或更高标准(如-std=c++11),并使用支持线程安全静态初始化的编译器版本。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:07:02