我需要快速遍历一棵树,并且我想并行执行。我宁愿使用并行扩展而不是手动启动一堆线程。
我当前的代码如下所示:
public void Traverse(Node root)
{
var nodeQueue = new Queue<Node>();
nodeQueue.Enqueue(root);
while (nodeQueue.Count!=0)
{
var node = nodeQueue.Dequeue();
if (node.Property = someValue) DoSomething(node);
foreach (var node in node.Children)
{
nodeQueue.Enqueue(node);
}
}
}
我真的希望 Parallel.ForEach 有一个 Parallel.While 模拟。我看到了 Stephen Toub 的关于使用 Parallel.ForEach 实现 Parallel While的文章。如果正确阅读,这仍然不起作用,因为我正在改变我试图迭代的队列。
我是否需要使用任务工厂和递归(这有风险吗?)?还是有一些我忽略的简单解决方案?
编辑:@svick
这棵树有超过 250,000 个节点。现在的最大深度是 14 个节点,包括根。
离根节点大约有 500 个节点,之后的余额具有相当随机的分布。我很快就会得到一些关于分布的更好的统计数据。
@谜:
是的,树正在被许多用户同时修改,但我通常会为树或子树设置一个共享读锁,或者允许脏读。
对 node.Children 的调用可以被认为是原子的。
DoSomething 实际上是几个委托之一,对于一些昂贵的操作,我可能会收集节点的快照列表并在遍历之外处理它们。
我意识到我可能应该查看一般情况(遍历子树而不是整个树。)为此,我在树的每个节点上运行遍历并查看总时间。
我为每个遍历算法使用了 Parallel.ForEach(nodes, Traverse),其中节点包含所有 ~250k 节点。这模拟(某种程度)许多用户同时请求许多不同的节点。
00256ms 广度优先顺序
00323ms 广度优先顺序与工作(我增加了一个静态计数器作为“工作”)
01495ms 柯克斯第一个答案
01143ms Svicks 第二个答案
00000ms 递归单线程在 60 秒后未完成
00000ms Enigmativity 的答案在 60 秒后没有完成
@Enigma,我认为我可能以某种方式弄乱了您的算法,因为它似乎应该更快。
结果至少可以说让我感到惊讶。我不得不在广度优先顺序上添加一些工作,只是为了让自己相信编译器并没有神奇地优化遍历。
对于头部的单次遍历,并行化第一级只有最好的性能。但几乎没有,随着我在第二级添加更多节点(2000 而不是 500),这个数字有所改善。