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

多进程并发场景下std::filesystem目录遍历不稳定问题排查

跨平台glob实现的并发稳定性问题及解决办法

问题现象

用std::filesystem递归遍历目录结合正则匹配实现跨平台glob功能时,单进程运行完全正常,但在同一机器后台并发运行100个该可执行文件进程(带不同参数)时,大量进程抛出如下错误并崩溃:

terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error'
  what():  filesystem error: cannot increment recursive directory iterator: No such file or directory
(core dumped)

改用boost::filesystem实现会出现同样问题,但依赖Unix原生glob.hpp库的glob方法运行稳定。

稳定的Unix原生glob实现代码

std::vector<std::string> glob(const std::string& pattern) {
    glob_t g;
    glob(pattern.c_str(), GLOB_TILDE, nullptr, &g); // 需确保glob返回0
    std::vector<std::string> filelist;
    filelist.reserve(g.gl_pathc);
    for (size_t i = 0; i < g.gl_pathc; ++i) {
        filelist.emplace_back(g.gl_pathv[i]);
    }
    globfree(&g);
    return filelist;
}

问题复现代码及步骤

最小可复现代码

#include <regex>
#include <filesystem>
#include <glob.h>
#include <iostream>

namespace fs = std::filesystem;

std::vector<std::string> glob(const std::string& regexPattern) {

    std::vector<std::string> matches;

    fs::path currentPath = fs::current_path();
    fs::path dirPath = regexPattern;
    if (dirPath.is_relative()) {
        dirPath = currentPath / dirPath;
    }

    size_t pos = 0;
    std::string path_upto_wildcard = "\"/\"";
    std::string str_dirPath = dirPath.string();
    while ((pos = str_dirPath.find("/")) != std::string::npos) {
        std::string token =str_dirPath.substr(0, pos);
        str_dirPath.erase(0, pos + 1);
        if (token == "" ) continue;
        if (token.find("*") !=  std::string::npos)  continue;

        path_upto_wildcard += token + "/";
    }

    dirPath = path_upto_wildcard;

    std::regex regEx(regexPattern);
    fs::recursive_directory_iterator endIterator;

    for (fs::recursive_directory_iterator it(dirPath); it != endIterator; ++it) {
        if ( std::regex_match(it->path().string(), regEx)) {
            matches.push_back(it->path().string());
        }
    }

    return matches;
}

int main() {
    std::vector dirs{"\"/mypath/a/.*XYZ*/.*\"",
                     "\"/mypath/b/.*XYZ*/.*\"",
                     "\"/mypath/c/.*XYZ*/.*\"",
                    };
    for (auto& dir : dirs) {
        std::vector<std::string> matches = glob(dir);
        for (auto& match : matches) {
            std::cout << match << std::endl;
        }
    }

    return 0;
}

编译命令

g++ test_globbing.cxx  -o test_globbing -std=c++17

复现方式

  1. 创建包含100行"./test_globbing"的test_glob.txt文件
  2. 运行以下shell脚本:
#!/bin/bash

n=0
while read arg; do echo $n && ((n+=1)); eval ' $arg &> job_$(echo $n).log &'; sleep .2; done < $1

该问题并非每次必现,但进程并发量越高,出现概率越大。

问题原因

  1. 文件系统竞态条件:并发遍历目录时,某进程刚获取到目录条目后,该条目(文件/子目录)可能被其他进程或系统操作删除/移动,导致迭代器递增时找不到目标,触发异常。
  2. 迭代器默认行为差异:std::filesystem::recursive_directory_iterator默认遇到无法访问的条目时直接抛出异常;而Unix原生glob库内部会处理这类临时不存在的条目,不会崩溃。
  3. 路径处理错误:复现代码中path_upto_wildcard初始值和dirs中的路径包含多余引号,会导致路径解析异常,并发场景下这种错误的影响被放大。

解决办法

1. 捕获迭代异常,忽略临时无效条目

修改迭代逻辑,添加异常捕获,跳过因文件临时不存在导致的错误:

// 构造迭代器时跳过权限不足的条目
fs::recursive_directory_iterator it(
    dirPath,
    fs::directory_options::skip_permission_denied
);

while (it != endIterator) {
    try {
        if (std::regex_match(it->path().string(), regEx)) {
            matches.push_back(it->path().string());
        }
        ++it;
    } catch (const fs::filesystem_error& e) {
        // 忽略临时不存在的文件/目录,继续迭代
        ++it;
    }
}

2. 修复路径处理的引号问题

去掉代码中多余的引号,确保路径解析正确:

// 修改path_upto_wildcard初始值
std::string path_upto_wildcard = "/";

// main函数中去掉路径的引号
std::vector dirs{"/mypath/a/.*XYZ*/.*",
                 "/mypath/b/.*XYZ*/.*",
                 "/mypath/c/.*XYZ*/.*",
                };

3. 优先使用平台原生实现(推荐)

如果需要跨平台,可做平台分支处理:Unix系统用原生glob,Windows系统用FindFirstFile系列API封装;或使用成熟的跨平台库(如Boost.Glob)。原生实现经过系统级优化,能更好处理并发场景下的文件系统竞态。

内容的提问来源于stack exchange,提问作者Mohamed Ayman Aly

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 13:28:12