类:
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();
}
问题:
这里有什么问题? 为什么节点在某些情况下为空? 它只在有很多生成的节点时才会发生。例如,如果只有10个节点,一切正常。
答案 0 :(得分:3)
你在这里有数据竞赛:
Parallel.ForEach(node.Nodes,
newRoot => { newRootNodes.Add(newRoot); });
添加到具有多个线程的列表不是线程安全的,并且会导致未确定的行为。
首先尝试使用简单的foreach
运行此部件,然后查看问题是否消失。运行两个嵌套的Parallel.ForEach
语句绝对是一个奇怪的选择。
答案 1 :(得分:3)
问题是你有这个代码:
Parallel.ForEach(node.Nodes, newRoot => { newRootNodes.Add(newRoot); });
允许多个线程同时向newRootNodes
列表添加项目。正如评论者指出的那样,List<T>
不是线程安全的。可能发生的是一个线程的Add
被另一个线程对Add
的调用中断,这导致列表中的内部索引递增。这会在列表的一个项目中留下null
值。
然后,在循环的后面你有:
rootNodes = newRootNodes;
将损坏的列表作为将要由while迭代的列表。
答案 2 :(得分:1)
List<T>
确实不是线程安全的,因此rootNode.Nodes.Add(childNode);
以不可预测的方式丢弃数据。
而不是使用List<>
使用ConcurrentBag<>
,它将全部奏效。请注意,ConcurrentBag<T>
无序,但这很好,因为您无论如何无法预测线程的顺序。