c++ std::filesystem directory walk under with concurrent processes

ffx8fchx  于 2023-06-07  发布在  其他
关注(0)|答案(1)|浏览(188)

我正在尝试用C++编写一个跨平台的globbing方法。为此,我尝试使用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;

        cleanPattern += token + "/";
        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;
}

当我编译并运行使用这个方法的可执行文件时,它运行得很好。但是,我在一个可执行文件中使用了这段代码,我希望在同一台机器上在后台并发运行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)

如果我使用std::filesystemboost::filesystem,就会发生这种情况。但是,如果我编写这个glob方法来使用依赖于unix的glob.hpp库,那么一切都能按预期运行,没有任何问题。glob.hpp代码:

std::vector<std::string> glob(const std::string& pattern) {
    glob_t g;
    glob(pattern.c_str(), GLOB_TILDE, nullptr, &g); // one should ensure glob returns 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

并且可以通过创建一些文本文件test_glob.txt来测试效果,该文本文件包含

./test_globbing

100次(每行一次),然后运行run_in_bkg.sh文件,其中包含:

#!/bin/bash/

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

这种影响并不总是存在,我也不能让它“总是”发生,但是随着正在运行的进程数量的增加,每运行几次,您就会看到一些作业失败。

42fyovps

42fyovps1#

不幸的是,该标准没有指定并发文件系统操作如何工作,特别是它使文件系统竞争未定义的行为。
幸运的是,还有另一种递增目录迭代器的方法:std::filesystem::recursive_directory_iterator::increment
它接受一个std::error_code参数并设置它,而不是抛出一个文件系统异常。所以,我相信你可以这样写,而不是你的for循环:

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

    std::error_code ec;
    do {
        it.increment(ec);
        // perhaps add a delay here
    } while (ec);
}

我不能重现你的问题,即使你最小的工作示例(我试图翻译到Windows作为最好的,我可以),所以我不保证什么。我也不确定如果it.increment失败了,它是否还会递增it,所以你可能需要像这样复制迭代器:

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

    std::error_code ec;
    auto it_copy = it;
    do {
        it.increment(ec);
        if (ec) {
            it = it_copy;
        }
        // perhaps add a delay here
    } while (ec);
}

相关问题