一种使用多线程在巨大树形结构中查找文件的方法

Approach for finding file in huge tree structure using multithreading

本文关键字:结构 大树 查找 方法 文件 巨大 一种 多线程      更新时间:2023-10-16

我有一个树,它有所有的目录和文件作为它的节点。我想搜索一个特定的文件。假设树分布广泛,我想使用多线程进行广度优先搜索,以找到一些特定的文件。我应该如何使用多线程来做到这一点?什么是好的方法?

在某些情况下,多线程搜索将提供有用的加速-例如,如果树跨越多个磁盘,或者如果某些磁盘/节点在某个网络上是间接的。

我当然不想尝试为每个文件夹创建线程。这是成千上万的创建/运行/终止,成千上万的堆栈分配/释放等等。总的来说,这是可以避免的开销。

可以进行多线程搜索,但正如其他海报所说,首先查看可用的替代方案。然后阅读这篇文章的其余部分。然后再看

我曾经使用类似于Matt建议的队列方法做过类似的事情。

我再也不想这样了:

我使用了一个生产者-消费者工作队列,其中有6个线程在等待工作(6个,因为测试表明这对我的问题来说是最佳的)。所有线程都是在启动时创建一次,从未终止。尽管性能不佳、关闭AV、216/217消息框等,但这些持续创建/加载/运行/等待/获取结果/终止IfYoureLucky的东西似乎都不受开发人员的欢迎。

这项工作以"folderSearch"类的形式出现,该类包含要搜索的路径、要调用的文件匹配函数事件和要进行搜索的FindFirst/FindNext循环方法。我在启动时在一个池中创建了几百个这样的池(即推到另一个P-C池队列:)。当FF/FN迭代文件夹中的文件以寻找匹配的文件时,遇到子文件夹导致从池中提取另一个folderSearch实例,并用新路径加载它;将它推送到工作队列中——然后某个线程会拾取它并迭代子文件夹。该类有一个匹配文件的路径列表,如果发现感兴趣的内容,还可以调用一个"results"事件(当然,将"this"作为参数)。如果一个folderSearch到了一根树枝的尽头,什么也没有找到,也没有什么可搜索的,它会把自己放回水池,(好吧,好吧,线程会做的,但你知道我的意思:)。

不需要任何明确的"负载平衡"。如果一个节点非常深,那么它自然会导致所有六个线程都在其子树上工作,因为其他路径都用完了。

搜索全部3个磁盘意味着从池中弹出3个folderSearch,用"C:\"、"E:\"和文件匹配方法加载它们,然后将它们推到工作队列中。然后磁盘发出嘎嘎的声音,事件最终会启动并产生结果。在我的案例(Windows)中,事件将folderSearch对象PostMessaged到一个UI线程,在该线程中,结果显示在treeView中,然后重新冷却folderSearch以供重用。

这个系统的速度是在3个磁盘上进行简单顺序搜索的2.5倍,即使是在我只有一个核心的旧开发盒上,因为所有3个磁盘都是并行搜索的。我怀疑它在现代盒子上也会显示出同样的优势,因为限制因素可能主要是磁盘上等待的IO。

令人惊讶的是,还有一个只有一个磁盘的加速,但没有那么多。不知道为什么——由于所有额外的复杂性,按理说应该更慢。

当然,也存在一些问题。一种是,对于激发大量结果的搜索,池将清空,因为UI无法跟上线程的步伐,因此所有folderSearch对象都卡在了排队到UI的PostMessages中,因此搜索线程的速度减慢,因为它们必须在池队列上等待,直到PostMessages得到处理,从而将folderSearch返回到池中。这也意味着UI被有效地阻止,直到搜索结束,它可以赶上,这否定了从一开始就线程化搜索的优势之一:(对于小的结果集,它工作得很好。

另一个可能的问题是,结果以一种"不自然"的方式返回,以一种目前令人困惑的方式交错,以至于像组装树视图这样的事情比单线程递归搜索要复杂得多——你必须到处乱跑,才能在正确的位置将结果填充到树视图中。这给GUI增加了额外的工作,并且可以抵消大量结果的搜索速度优势,正如我发现的

这种设计可以同时运行多个搜索。作为一个测试,我会同时加载几个3个磁盘的搜索,(不,不是在加载treeView时——我只是把找到的文件数转储到GUI消息处理程序的备忘录行上)。这引起了巨大的震动,使一切都慢了下来,但它最终完成了所有搜索,没有崩溃。我不经常这样做,因为我害怕我糟糕的磁盘。不要在家里尝试

我从来都不知道有多少线程可以挂在队列上。在我的装有本地磁盘的旧盒子上,六个大约是最佳的。如果混合中有网络磁盘,那么可能越多越好,因为网络调用阻塞一个线程的时间往往比本地磁盘读取的时间长得多。从未尝试过,但加载更多的线程并不会影响本地磁盘的性能,只是使用了更多的内存,没有额外的优势。

另一个问题是找出搜索是否真的结束了——所有的结果都在。。还是某个线程仍在网络驱动器上等待,而该驱动器速度较慢或实际上无法访问?只需一次搜索,我就能判断出来,因为搜索结束后,池再次变满(我将池级别转储到1s GUI计时器上的stausBar)。这在我的应用程序中并不重要,但在其他应用程序中,它可能。。。

取消搜索也是一个类似的问题。这类事情需要另一个"searchClass"来控制每次搜索。分配给搜索的每个folderSearch都必须保留对searchClass的引用,这样任何处理folderSearch的线程都可以发现是否设置了中止,如果设置了中止则停止使用该folderSearch。我不需要这个,所以没有实现它。

存在错误报告。如果一个网络驱动器连接失败,例如,几个(很可能是所有!),线程可能会在引发异常之前阻塞很长一段时间。然后,他们除了一下子。catch消息被加载到folderSearch的"errorMess"字段中,结果事件被触发。人类可检测到的证据——嘎嘎声停止了。一分钟内什么都不发生,然后[线程数]错误同时出现。

注意其他海报上的注意事项和我的经历。只有当你真的、真的需要这样的东西用于某些特殊的搜索目的时,才可以尝试你对多线程应用程序100%满意。如果你可以通过单线程搜索,或者对文件资源管理器的shell调用,或者几乎任何其他方式逃脱惩罚,那就这样做吧!

自从使用FTP服务器生成树以来,我一直使用相同的方法。这也快得多,尽管服务器管理员可能对的多个连接不满意

Rgds,Martin

多线程处理在每个分支中具有未知工作分布的树搜索任务是不平凡的(这在约束满足问题中会出现很多问题)

最简单的方法是创建一个任务队列(由互斥对象保护)。用根节点的所有子节点填充这个队列。生成N个线程(每个可用的CPU核心一个),并让它们搜索每个节点。您可以使用各种技巧来避免一些糟糕的情况(如果任何线程发现其节点"出乎意料地深",您可以让他们将新任务添加到与它希望其他线程探索的子目录相对应的队列中。)如果你的节点深度分布良好,并且根节点有很多子节点,你可以完全避开队列——只需为每个索引为i的线程分配探索X%N+i的任务(其中X是根节点的子节点数。)

我的第一个响应是说"只使用nftw,忘记多线程"。如果您碰巧有一个以多线程方式执行树遍历的nftw实现,那么您可以免费获得多线程(我不知道有任何这样的实现)。如果你真的想做多个线程,我建议你使用nftw,并在回调中为每个目录生成一个新的线程,但目前还不清楚这是否比遵循Kanopus的建议更容易(或有任何不同)。在思考了一会儿之后,我回到了我的第一个建议,并想知道为什么要用多个线程来做这件事。拥有更多的线程不太可能加快搜索速度。使用nftw。别担心穿线。

假设树中的每个节点都代表一个目录(以及其中的文件),并且假设可以打开的线程数量没有限制:

输入根节点,如果它有n子目录,则创建n - 1线程以在第一个n - 1中搜索,并继续搜索最后一个子目录。根据需要重复。

树结构通常不适合并行化。假设你已经将所有节点加载到内存中,试着将它们组织起来,使它们占据一个数组——毕竟,它们需要存在于串行的RAM中——为了搜索的目的,忽略它们的树结构。然后使用某种并行的for循环迭代数组的元素。对此,一个流行的选择是OpenMP,或者您可以在Visual Studio中尝试parallel_for_each