简体   繁体   English

C#多线程树遍历

[英]C# Multi-Threaded Tree traversal

I am trying to write a C# system that will multi-threaded traverse a tree structure. 我正在尝试编写一个C#系统,该系统将多线程遍历树结构。 Another way to look at this is where the consumer of the BlockingCollection is also the producer. 另一种看待这种情况的方式是BlockingCollection的消费者也是生产者。

The problem I am having is telling when everything is finished. 我遇到的问题是告诉一切完成的时间。 The test I really need is to see if all the threads are on the TryTake. 我真正需要的测试是查看是否所有线程都在TryTake上。 If they are then everything has finished, but I cannot find a way to test of this or wrap this with anything that would help achieve this. 如果是这样,那么一切都已完成,但是我无法找到一种方法来测试或用任何有助于实现此目标的方法进行包装。

The code below is a very simple example of this code as far as I have it, but there is a condition in which this code can fail. 就我所知,下面的代码是该代码的一个非常简单的示例,但是在某些情况下该代码可能会失败。 If the first thread just passed the test.TryTake(out v,-1) and has not yet executed the s.Release(); 如果第一个线程刚刚通过了test.TryTake(out v,-1)并且尚未执行s.Release(); and it just pulled the last item from the collection, and the second thread just performed the if(s.CurrentCount == 0 && test.Count ==0) this could return true, and incorrectly start finishing things up. 并且它只是从集合中提取了最后一个项目,而第二个线程仅执行了if(s.CurrentCount == 0 && test.Count == 0),这可能返回true,并错误地开始整理工作。

But then the first thread would continue on and try and add more to the collection. 但是,第一个线程将继续运行,并尝试向集合中添加更多线程。

If I could make the lines: 如果我能做的话:

if (!test.TryTake(out v, -1))
     break;
s.Release();

atomic then I believe this code would work. 原子的,那么我相信这段代码会起作用。 (Which is obviously not possible.) (这显然是不可能的。)

But I cannot figure out how to fix this flaw. 但是我不知道如何解决这个缺陷。

class Program
{

    private static BlockingCollection<int> test;

    static void Main(string[] args)
    {
        test = new BlockingCollection<int>();

        WorkClass.s = new SemaphoreSlim(2);

        WorkClass w0 = new WorkClass("A");
        WorkClass w1 = new WorkClass("B");

        Thread t0 = new Thread(w0.WorkFunction);
        Thread t1 = new Thread(w1.WorkFunction);

        test.Add(10);

        t0.Start();
        t1.Start();

        t0.Join();
        t1.Join();

        Console.WriteLine("Done");

        Console.ReadLine();
    }

    class WorkClass
    {
        public static SemaphoreSlim s;

        private readonly string _name;

        public WorkClass(string name)
        {
            _name = name;
        }

        public void WorkFunction(object t)
        {
            while (true)
            {
                int v;

                s.Wait();

                if (s.CurrentCount == 0 && test.Count == 0)
                    test.CompleteAdding();

                if (!test.TryTake(out v, -1))
                    break;
                s.Release();


                Console.WriteLine(_name + " =  " + v);
                Thread.Sleep(5);
                for (int i = 0; i < v; i++)
                    test.Add(i);
            }

            Console.WriteLine("Done " + _name);
        }
    }
}

This can be parallelized using task parallelism. 这可以使用任务并行性并行化。 Every node in the tree is considered to be a task which may spawn sub-tasks. 树中的每个节点都被认为是可以产生子任务的任务。 See Dynamic Task Parallelism for a more detailed description. 有关更多详细说明,请参见动态任务并行性。

For a binary tree with 5 levels that writes each node to console and waits for 5 milliseconds as in your example, the ParallelWalk method would then look for example as follows: 对于具有5个级别的二叉树,该树将每个节点写入控制台并等待5毫秒(如您的示例中所示),则ParallelWalk方法将如下所示:

class Program
{
    internal class TreeNode
    {

      internal TreeNode(int level)
      {
        Level = level;
      }

      internal int Level { get; }
    }

    static void Main(string[] args)
    {
      ParallelWalk(new TreeNode(0));

      Console.Read();
    }

    static void ParallelWalk(TreeNode node)
    {
      if (node == null) return;

      Console.WriteLine(node.Level);
      Thread.Sleep(5);

      if(node.Level > 4) return;

      int nextLevel = node.Level + 1;
      var t1 = Task.Factory.StartNew(
                 () => ParallelWalk(new TreeNode(nextLevel)));
      var t2 = Task.Factory.StartNew(
                 () => ParallelWalk(new TreeNode(nextLevel)));

      Task.WaitAll(t1, t2);
    }
}

The central lines are where the tasks t1 and t2 are spawned. 中心线是任务t1和t2的生成位置。

By this decomposition in tasks, the scheduling is done by the Task Parallel Library and you don't have to manage a shared set of nodes anymore. 通过任务的这种分解,调度由任务并行库完成 ,您不再需要管理一组共享的节点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM