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

使用C++ std::filesystem异步递归计数动态文件系统遇/proc异常求解决

问题

我尝试寻找一种在动态变化的文件系统中递归列出并计数文件的方法,但在/proc目录中不断遇到错误。

#include <list>
#include <unordered_set>

class directory_pile : public std::unordered_set<std::string>
{
    std::mutex usage_mutex;

public:
    bool contains(const std::string &dir_name)
    {
        std::lock_guard lg(usage_mutex);
        if (this->find(dir_name) == this->end())
        {
            return false;
        }
        return true;
    }

    void add(const std::string &dir_name)
    {
        std::lock_guard lg(usage_mutex);
        this->emplace(dir_name);
    }
};

static int count_files(const std::string &path, directory_pile *dir_pile)
{
    if (dir_pile->contains(path))
    {
        return 0;
    }
    dir_pile->add(path);
    int i = 0;
    std::list<std::future<int>> futures;
    for (const auto &entry : std::filesystem::directory_iterator(path))
    {
        if (entry.is_directory() && entry.exists())
        {
            const std::string p = entry.is_symlink() ? std::filesystem::canonical(entry.path()) : entry.path();
            futures.push_back(std::async(count_files, p, dir_pile)); // std::async(count_files, entry.path(), dir_pile));
        }
        else
        {
            i++;
        }
    }
    for (auto &f : futures)
    {
        i += f.get();
    }
    return i;
}

int main()
{
    directory_pile *dir_pile = new directory_pile();
    std::cout << count_files(std::filesystem::current_path(), dir_pile) << std::endl;
}

directory_pile类用于记录已访问的目录,以处理循环符号链接(我的环境中存在指向根目录/的符号链接)。但程序不断抛出如下异常:

terminate called after throwing an instance of 'std::filesystem::__cxx11::filesystem_error'
  what():  filesystem error: cannot make canonical path: No such file or directory [/proc/91169/fd/14]
Aborted

这完全合理,因为/proc目录中的文件会在代码运行时不断增减。我尝试将canonical路径的调用包裹在try-catch块中,但这导致程序无限运行,因为它会持续搜索新创建的进程。

我看到有帖子提到可以用nftw实现,但这会让异步处理部分变得非常繁琐。请问是否有基于std::filesystem的解决方案?

解决方案

针对/proc这类动态虚拟文件系统带来的问题,可以通过以下基于std::filesystem的方案解决:

1. 跳过特殊虚拟文件系统

直接识别并跳过/proc、/sys、/dev这类动态变化的虚拟文件系统,从根源避免无限遍历和动态条目消失的问题。

2. 分层异常捕获

在目录遍历、单个条目访问、符号链接解析、异步任务获取结果等各个环节添加异常捕获,确保单个条目或任务出错不会导致整个程序终止。

3. 明确异步执行策略

使用std::launch::async明确指定异步执行,避免默认策略可能导致的同步执行,同时限制异步任务的范围。

修正后的代码示例

#include <list>
#include <unordered_set>
#include <mutex>
#include <future>
#include <filesystem>
#include <iostream>
#include <stdexcept>

namespace fs = std::filesystem;

class directory_pile : public std::unordered_set<std::string>
{
    std::mutex usage_mutex;

public:
    bool contains(const std::string &dir_name)
    {
        std::lock_guard lg(usage_mutex);
        return find(dir_name) != end();
    }

    void add(const std::string &dir_name)
    {
        std::lock_guard lg(usage_mutex);
        emplace(dir_name);
    }
};

// 判断是否为需要跳过的特殊虚拟文件系统
bool is_special_filesystem(const fs::path& path) {
    std::string path_str = path.string();
    return path_str.starts_with("/proc/") || path_str.starts_with("/sys/") || path_str.starts_with("/dev/");
}

static int count_files(const fs::path &path, directory_pile *dir_pile)
{
    // 跳过特殊文件系统,避免动态条目和无限遍历
    if (is_special_filesystem(path)) {
        return 0;
    }

    std::string path_str = path.string();
    if (dir_pile->contains(path_str))
    {
        return 0;
    }
    dir_pile->add(path_str);

    int file_count = 0;
    std::list<std::future<int>> futures;

    try {
        for (const auto &entry : fs::directory_iterator(path))
        {
            try {
                // 跳过已消失的动态条目
                if (!entry.exists()) {
                    continue;
                }

                if (entry.is_directory())
                {
                    fs::path target_path;
                    if (entry.is_symlink()) {
                        try {
                            // 安全解析符号链接,失败则跳过
                            target_path = fs::canonical(entry.path());
                        } catch (const fs::filesystem_error&) {
                            continue;
                        }
                    } else {
                        target_path = entry.path();
                    }

                    // 明确指定异步执行策略
                    futures.push_back(std::async(std::launch::async, count_files, target_path, dir_pile));
                }
                else
                {
                    file_count++;
                }
            } catch (const fs::filesystem_error&) {
                // 单个条目访问出错,直接跳过
                continue;
            }
        }
    } catch (const fs::filesystem_error&) {
        // 整个目录无法遍历,返回已计数的文件数
        return file_count;
    }

    for (auto &f : futures)
    {
        try {
            // 捕获异步任务的异常,避免程序终止
            file_count += f.get();
        } catch (...) {
            continue;
        }
    }

    return file_count;
}

int main()
{
    directory_pile dir_pile;
    std::cout << count_files(fs::current_path(), &dir_pile) << std::endl;
    return 0;
}

关键改进点

  • 跳过特殊文件系统:通过is_special_filesystem函数直接排除/proc等动态目录,避免无限遍历新进程和动态条目消失的问题
  • 分层异常处理:在遍历目录、处理单个条目、解析符号链接、获取异步结果时都添加了异常捕获,确保局部错误不影响整体流程
  • 明确异步策略:使用std::launch::async确保任务真正异步执行,同时避免默认策略的不确定性
  • 内存优化:main函数中不再使用动态分配的directory_pile,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 20:44:52