并行树打印方法中的错误

时间:2012-01-18 12:49:29

标签: c# .net multithreading tree

类:

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个节点,一切正常。

3 个答案:

答案 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> 无序,但这很好,因为您无论如何无法预测线程的顺序。