【问题标题】:Preload data from a file using a separate thread使用单独的线程从文件中预加载数据
【发布时间】:2011-08-20 12:45:36
【问题描述】:

我有一个处理大量(相对较小)文件的小型应用程序。它按顺序运行:它从文件加载数据,对其执行操作,然后移动到下一个文件。 我注意到在运行期间,CPU 使用率并不是 100%,我猜这是由于硬盘驱动器上的 I/O 操作所花费的时间。

因此,我们的想法是使用单独的线程将下一个数据加载到内存中,与当前数据的处理并行(所讨论的数据只是一个 int 序列,存储在一个向量中)。这似乎是一个非常普遍的问题,但我很难找到一个简单、普通的 C++ 示例来做到这一点! 现在 C++0x 即将推出,使用新线程工具的简单演示代码,无需外部库,会非常好。

另外,虽然我知道这取决于很多事情,但是否有可能对这种方法的好处(或挫折)进行有根据的猜测,例如要加载的数据文件的大小?我猜对于大文件,磁盘 I/O 操作无论如何都很少,因为数据已经被缓冲(使用 fstream(?))

奥利维尔

【问题讨论】:

    标签: c++ multithreading c++11


    【解决方案1】:

    一个关于如何使用一些 C++0x 线程和同步工具的玩具程序。不知道这个性能如何(我推荐马特的回答),为了举例,我的重点是清晰和正确。

    根据您的要求,文件是单独读取的。但是,它们不会转换为int 的序列,因为我觉得这与处理而不是严格的 I/O 更相关。所以文件被转储到一个普通的std::string

    #include <fstream>
    #include <sstream>
    #include <string>
    #include <vector>
    #include <deque>
    #include <future>
    #include <mutex>
    #include <condition_variable>
    
    int
    main()
    {
        // this is shared
        std::mutex mutex;
        std::condition_variable condition;
        bool more_to_process = true;
        std::deque<std::string> to_process;
    
        /* Reading the files is done asynchronously */
        std::vector<std::string> filenames = /* initialize */
        auto process = std::async(std::launch::async, [&](std::vector<std::string> filenames)
        {
            typedef std::lock_guard<std::mutex> lock_type;
            for(auto&& filename: filenames) {
                std::ifstream file(filename);
                if(file) {
                    std::ostringstream stream;
                    stream << file.rdbuf();
                    if(stream) {
                        lock_type lock(mutex);
                        to_process.push_back(stream.str());
                        condition.notify_one();
                    }
                }
            }
            lock_type lock(mutex);
            more_to_process = false;
            condition.notify_one();
        }, std::move(filenames));
    
        /* processing is synchronous */
        for(;;) {
            std::string file;
            {
                std::unique_lock<std::mutex> lock(mutex);
                condition.wait(lock, [&]
                { return !more_to_process || !to_process.empty(); });
    
                if(!more_to_process && to_process.empty())
                    break;
                else if(to_process.empty())
                    continue;
    
                file = std::move(to_process.front());
                to_process.pop_front();
            }
    
            // use file here
        }
    
        process.get();
    }
    

    一些注意事项:

    • 互斥体、条件变量、停止标志和std::string容器都是逻辑相关的。你也可以用线程安全的容器/通道替换它们
    • 我使用std::async 而不是std::thread,因为它具有更好的异常安全特性
    • 没有错误处理可言;如果由于某种原因无法读取文件,则会静默跳过该文件。您有几个选择: 发出信号,表示没有更多需要处理并尽快抛出处理;或使用boost::variant&lt;std::string, std::exception_ptr&gt; 将错误传递给事物的处理端(此处错误作为异常传递,但您可以使用error_code 或任何您喜欢的东西)。无论如何都不是一份详尽的清单。

    【讨论】:

    • 好吧,我认为这很好地回答了我的问题!非常好的一段代码。谢谢。
    【解决方案2】:

    对这样的 IO 绑定问题使用线程只会给您带来微不足道的性能提升。您可以通过提前打开多个文件并通过重叠系统调用(如您所指示的那样)通过线程来填补您希望饱和可用 IO 资源的一些“空白”。

    我建议您改为向内核提供有关您打算如何执行 IO 的提示,这将改善预读,并提高物理读取带宽,例如通过验证文件系统、内核和硬盘驱动器(或任何您的存储源)尽可能快。

    【讨论】:

    • 看起来很有趣。但我看不出它是如何与 STL (fstream) 集成的。此外,对这些函数的调用似乎是阻塞的。
    【解决方案3】:

    我会创建两个线程和两个缓冲区:

    • 首先将数据从文件读取到缓冲区的人
    • 第二个处理接收到的数据

    如果文件不适合缓冲区,只需添加文件结束标志。如果第二个线程在缓冲区的末尾没有找到它,它应该从第二个线程读取它。

    缓冲区的数量和大小以及可能的线程数是需要优化的参数。 主要思想是让磁盘控制器连续工作。

    ** 编辑 **

    理想的情况是您将所有的执行时间都花在了从 HDD 读取数据上。但是,它取决于“每个数据部分的执行时间”/“每个数据部分的 HDD 读取时间”,因为这可能会有所不同。

    【讨论】:

      【解决方案4】:

      由于您的文件大小相对较小并且您必须处理文件数量,因此更好的设计是创建两个线程,

      1.第一个线程只读取和处理放置在偶数的文件 在文件列表中(*nix 中的 ls -l)。 2. 第二个线程读取列表中奇怪放置的文件。

      您提到的“一个线程将数据读取到向量中,另一个线程从中读取”的方法的缺点是您必须关注线程竞争,并且需要通过使用互斥锁和条件变量来防止它。

      由于这种方法不需要任何锁定[希望文件之间的数据之间没有依赖关系]

      此外,从文件中读取数据的更快方法是将文件二进制读取到合适大小的缓冲区中。

      希望答案对你有所帮助。

      **编辑:**

      根据您的评论,您似乎必须使用一个线程将数据读取到队列数据结构中[可能是一个 char 缓冲区队列],而第二个线程从队列中读取数据并进行处理。

      如前所述,问题是从同一个队列读取和写入,因为 STL 容器不是线程安全的。

      所以我可以在这里推荐的是管理你的共享数据结构,即在这里使用 locaks 排队,其他一切都可以:

      1. Boost Lock免费: Boost lock free 2. 编写自己的无锁实现: Lock free impl

      【讨论】:

      • 我没有明确提及,但我假设数据之间存在依赖关系,或者至少文件必须按特定顺序处理。否则,您建议的策略确实非常明智,并且正如您所指出的,它避免了处理互斥锁的麻烦......
      猜你喜欢
      • 2022-01-22
      • 2015-09-02
      • 1970-01-01
      • 1970-01-01
      • 2016-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多