使用单独的线程从文件预加载数据

时间:2011-08-20 12:45:37

标签: c++ multithreading c++11

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

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

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

奥利弗

4 个答案:

答案 0 :(得分:4)

关于如何使用某些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<std::string, std::exception_ptr>将错误传递给事物的处理方(此处错误作为例外传递,但您可以使用error_code或您想要的任何内容)。无论如何都不是详尽的清单。

答案 1 :(得分:2)

对这样的IO绑定问题使用线程将使您获得可忽略的性能提升。您可以通过提前打开几个文件,并通过重叠系统调用,通过您指示的线程来填补可用IO资源的一些“空白”。

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

答案 2 :(得分:1)

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

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

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

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

**编辑**

理想情况是,您在从HDD读取数据时花费了所有执行时间。然而,这取决于“每个基准部分的进行时间”/“每个基准部分的HDD读取时间”,因为这可能会变化。

答案 3 :(得分:0)

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

1. First thread reading and processing only files placed at even number 
in the file listing (ls -l in *nix).
2. Second thread reading the oddly placed file in the listing.

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

这个方法不需要任何锁定[希望文件之间没有依赖关系]

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

希望答案可以帮到你。

**编辑:**

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

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

所以我在这里建议的是管理你的共享数据结构,即使用locaks排队,其他所有内容都是:

1. Boost Lock free :  Boost lock free 
2. Write your own loack free implementation :  Lock free impl