【问题标题】:Multiple Threads searching on same folder at same time多个线程同时搜索同一文件夹
【发布时间】:2015-06-22 03:25:50
【问题描述】:

目前我有一个大约 170,000 个 jpg 文件名的 .txt 文件,我将它们全部读入一个列表 (fileNames)。

我想搜索一个文件夹(这个文件夹有子文件夹)来检查 fileNames 中的每个文件是否存在于这个文件夹中,如果存在,请将其复制到一个新文件夹中。

我做了一个粗略的估计,但每次搜索和复制 fileNames 中的每个文件名大约需要 0.5 秒。所以 170,000 秒大约是 48 小时,所以除以 2 后,我的应用需要大约 24 小时才能使用 1 个线程搜索每个文件名!显然这太长了,所以我想缩小范围并加快进程。使用多线程执行此操作的最佳方法是什么?

目前我正在考虑创建 20 个单独的线程并将我的列表(文件名)拆分为 20 个不同的列表并同时搜索文件。例如,我将有 20 个不同的线程同时执行以下操作:

            foreach (string str in fileNames)
            {
                foreach (var file in Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories))
                {
                    string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
                    if (!File.Exists(combinedPath))
                    {
                        File.Copy(file, combinedPath);
                    }
                }
            }

更新后在下方显示我的解决方案:

            string[] folderToCheckForFileNames = Directory.GetFiles("C:\\Users\\Alex\\Desktop\\ok", "*.jpg", SearchOption.AllDirectories);

            foreach(string str in fileNames)
            {
                Parallel.ForEach(folderToCheckForFileNames, currentFile =>
                  {
                      string filename = Path.GetFileName(currentFile);
                      if (str == filename)
                      {
                          string combinedPath = Path.Combine(targetDir, filename);
                          if (!File.Exists(combinedPath))
                          {
                              File.Copy(currentFile, combinedPath);
                              Console.WriteLine("FOUND A MATCH AND COPIED" + currentFile);
                          }
                      }

                  }
                );

            }

感谢大家的贡献!非常感谢!

【问题讨论】:

  • 如果我没看错的话,为什么不将所有文件名读入内存,比如 HashSet,然后用它来搜索文件。至于使用多线程加速磁盘 IO,仅此而已。一旦磁盘 IO 被最大化,你有多少线程都没有关系。
  • 不仅仅是磁盘 IO,它还严重依赖于可用于处理线程逻辑的处理核心的数量,因此最终是一个糟糕的解决方案
  • 您尝试过使用 TPL foreach 吗? msdn.microsoft.com/en-us/library/dd460720(v=vs.110).aspx
  • 所以你们是说实际上将所有实际的 .jpg 文件从 folderToCheckForFileName 读入内存并进行搜索?而不是检查我机器上的实际文件夹?
  • 不要读取文件,只读取文件名列表

标签: c# multithreading file-search


【解决方案1】:

您应该使用并行 linq,而不是使用普通的 foreach 语句进行搜索。 Parallel linq 结合了 LINQ 语法的简单性和可读性与并行编程的强大功能。就像针对任务并行库的代码一样。这将保护您免受低级线程操作和可能的异常(难以找到/调试异常)的影响,同时将您的工作分配给多个线程。所以你可能会这样做:

fileNames.AsParallel().ForAll(str =>
            {
                var files = Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories);
                files.AsParallel().ForAll(file =>
                {
                    if (!string.IsNullOrEmpty(file))
                    {
                        string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
                        if (!File.Exists(combinedPath))
                        {
                            File.Copy(file, combinedPath);
                        }
                    }
                });
            });

【讨论】:

  • 感谢上面的 qamar,我使用了一个并行的 foreach 循环。这似乎是同一个概念,只是对我来说比我的代码更容易阅读。我将在上面发布我的代码更新。我通过 qamar 提出的解决方案与您的解决方案有什么区别?
  • 它们之间并没有太大的区别。它们都是循环结构,尽管Parallel.Foreach() 更受欢迎。但是Parallel.ForAll() is usually used at the end of a possible complex PLINQ query. So Parallel.Foreach()` 对你来说是一个更好的选择
【解决方案2】:

如果您的计算机的内核少于 20 个,则 20 个不同的线程将无济于事。事实上,它会使进程变慢,因为您将 1)必须花时间在每个线程之间进行上下文切换(这是您的 CPU 模拟超过 1 个线程/内核的方式)和 2).NET 中的 Thread 保留 1 MB 的堆栈,相当庞大。

相反,尝试将您的 I/O 划分为 async 工作负载,将 Task.Run 用于 CPU 密集型/密集型部分。另外,将Tasks 的数量保持在最多 4 到 8 个。

示例代码:

var tasks = new Task[8];
var names = fileNames.ToArray();
for (int i = 0; i < tasks.Length; i++)
{
    int index = i;
    tasks[i] = Task.Run(() =>
    {
        for (int current = index; current < names.Length; current += 8)
        {
            // execute the workload
            string str = names[current];
            foreach (var file in Directory.GetFiles(folderToCheckForFileName, str, SearchOption.AllDirectories))
            {
                string combinedPath = Path.Combine(newTargetDirectory, Path.GetFileName(file));
                if (!File.Exists(combinedPath))
                {
                    File.Copy(file, combinedPath);
                }
            }
        }
    });
}
Task.WaitAll(tasks);

【讨论】:

  • 我改为使用并行 foreach 循环。我会记住这一点,以便将来尝试这种方法谢谢。两者之间有什么优缺点?
  • 嗯,看来您实际上可能选择了better solution。不同之处在于Parallel.ForEach 是同步的并且阻塞直到一切都完成,但我们已经这样做了,因为我们在最后做了一个Task.WaitAll。此外,Parallel.ForEach 使用Partitioner&lt;T&gt; 来平均分配任务。有关详细信息,请参阅我发布的链接。
猜你喜欢
  • 1970-01-01
  • 2018-11-16
  • 1970-01-01
  • 1970-01-01
  • 2015-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多