并行树打印方法中的错误
本文关键字:错误 方法 打印 并行 | 更新日期: 2023-09-27 18:20:34
类:
public class Tree
{
public Node RootNode { get; set; }
}
public class Node
{
public int Key { get; set; }
public object Value { get; set; }
public Node ParentNode { get; set; }
public List<Node> Nodes { get; set; }
}
方法:
此方法生成一个树。
private static int totalNodes = 0;
static Tree GenerateTree()
{
Tree t = new Tree();
t.RootNode = new Node();
t.RootNode.Key = 0;
t.RootNode.Nodes = new List<Node>();
Console.WriteLine(t.RootNode.Key);
List<Node> rootNodes = new List<Node>();
rootNodes.Add(t.RootNode);
while (totalNodes <= 100000)
{
List<Node> newRootNodes = new List<Node>();
foreach (var rootNode in rootNodes)
{
for (int j = 0; j < 3; j++)
{
totalNodes++;
Console.Write(string.Format(" {0}({1}) ", totalNodes, rootNode.Key));
Node childNode = new Node() {Key = totalNodes, Nodes = new List<Node>(), ParentNode = t.RootNode};
rootNode.Nodes.Add(childNode);
newRootNodes.Add(childNode);
}
Console.Write(" ");
}
Console.WriteLine();
rootNodes = newRootNodes;
}
return t;
}
这个方法应该打印一个树,但在某些情况下节点为空:
static void PrintTreeParallel(Node rootNode)
{
List<Node> rootNodes = new List<Node>();
List<Node> newRootNodes = new List<Node>();
rootNodes.Add(rootNode);
Console.WriteLine(rootNode.Key);
while (rootNodes.Count > 0)
{
newRootNodes = new List<Node>();
Parallel.ForEach(rootNodes, node =>
{
if (node != null)
{
Console.Write(string.Format(" {0} ", node.Key));
if (node.Nodes != null)
Parallel.ForEach(node.Nodes,
newRoot => { newRootNodes.Add(newRoot); });
}
else
{
//HOW CAN WE GET HERE?????
Debugger.Break();
Console.WriteLine(rootNodes.Count);
}
});
Console.WriteLine();
rootNodes = newRootNodes;
}
}
执行:
static void Main(string[] args)
{
var t = GenerateTree();
Console.WriteLine("Tree generated");
PrintTreeParallel(t.RootNode);
Console.WriteLine("Tree printed paral");
Console.ReadLine();
}
问题:
这里怎么了为什么在某些情况下节点为null?只有当有大量生成的节点时,才会发生这种情况。例如,如果只有10个节点,则一切正常。
问题是您有以下代码:
Parallel.ForEach(node.Nodes, newRoot => { newRootNodes.Add(newRoot); });
这允许多个线程同时向newRootNodes
列表中添加项目。正如一位评论者指出的,List<T>
不是线程安全的。可能发生的情况是,一个线程的Add
被另一个线程对Add
的调用中断,这导致列表中的内部索引增加。这会在列表的某个项目中留下一个null
值。
然后,在循环的后面,你有:
rootNodes = newRootNodes;
这将损坏的列表作为要在while之前迭代的列表。
这里有一场数据竞赛:
Parallel.ForEach(node.Nodes,
newRoot => { newRootNodes.Add(newRoot); });
添加到具有多个线程的列表不是线程安全的,并且会导致不确定的行为。
首先试着用一个简单的foreach
运行这个部分,看看问题是否消失了。运行两个嵌套的Parallel.ForEach
语句无疑是一个奇怪的选择。
List<T>
确实不是线程安全的,因此rootNode.Nodes.Add(childNode);
正在以不可预测的方式丢弃数据。
不用List<>
,用ConcurrentBag<>
就可以了。请注意,ConcurrentBag<T>
是无序的,但这很好,因为无论如何都无法从线程中预测顺序。