繁体   English   中英

递归任务

[英]Tasks in Recursion

我具有递归遍历二叉树的功能。 由于操作是计算密集型的,因此我想到使用以下任务在递归函数中生成多个线程:

static void Traverse<T>(Tree<T> node, Action<T> action) 
{ 
 if (node == null) return; 
 var t1 = Task.Factory.StartNew(() => action(node.Data)); 
 var t2 = Task.Factory.StartNew(() => Traverse(node.Left, action)); 
 var t3 = Task.Factory.StartNew(() => Traverse(node.Right, action)); 
 Task.WaitAll(t1, t2, t3); 
} 

现在看来确实可行。 但是我想知道在以递归方式使用任务时是否需要注意任何事项。 例如,如果树的深度很长,它是否会以某种方式无法创建较低级别的任务并等待其他任务完成(因为它们正在等待较低级别的任务完成,因此永远无法完成)?

如果生成的树很大,那么许多任务可能会导致问题完全耗尽整个线程池,从而导致其他地方的性能问题,这是因为节点及其父节点之间没有依赖关系,因此所有节点都将尝试同时运行。 我要做的是让您的Tree<T>类实现IEnumerable<T> ,该方法将返回其自己的Data属性以及所有其子级的Data属性,然后使用Parallel.ForEach

static void Traverse<T>(Tree<T> node, Action<T> action) 
{
    Parallel.ForEach(node, action);
}


//Elsewhere
class Tree<T> : IEnumerable<T>
{
    Tree<T> Left { get; set; }
    Tree<T> Right { get; set; } 
    T Data { get; set; }

    public IEnumerator<T> GetEnumerator()
    {
        yield return this.Data;

        if (Left != null)
        {
            foreach (var left in Left)
            {
                yield return left.Data;
            }
        }

        if (Right != null)
        {
            foreach (var right in Right)
            {
                yield return right.Data;
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

您需要关注的唯一“ Gotcha”是,如果树中存在任何闭环,其中子代可能是更高级别节点的父代,这将导致无限递归。


编辑 :这是一个新版本,它不对GetEnumerator使用递归,而是使用Stack<Tree<T>>对象来保存状态,因此,如果您有极高的树,则不能有StackOverflowException 同样,如果您从注释行中删除注释,它将停止以前版本的“无限递归”问题。 但是,如果您知道没有任何循环结构,则没有必要,因此我将其注释掉。

class Tree<T> : IEnumerable<T>
{
    Tree<T> Left { get; set; }
    Tree<T> Right { get; set; }
    T Data { get; set; }

    public IEnumerator<T> GetEnumerator()
    {
        Stack<Tree<T>> items = new Stack<Tree<T>>();
        //HashSet<Tree<T>> recursiveCheck = new HashSet<Tree<T>>();

        items.Push(this);
        //recursiveCheck.Add(this);

        while (items.Count > 0)
        {
            var current = items.Pop();

            yield return current.Data;

            if (current.Left != null)
                //if(recursiveCheck.Add(current.Left))
                    items.Push(current.Left);
            if (current.Right != null)
                //if (recursiveCheck.Add(current.Right))
                    items.Push(current.Right);
        }

    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

就像您说的那样,递归地生成线程似乎不是一个好主意,如果树足够长,您将得到很多线程,这会变慢,因为会有很多开销,或者最终达到程序中并行线程的极限。 因此,我建议您改用ThreadPool来管理线程。

您可能有一个线程来导航树,另外两个线程来完成繁重的工作。 您还应该注意,除非您有一些阻止操作(例如I / O读/写或正在进行一些网络连接),否则使用线程将不是很好。 如果您不这样做,则最好只使用一个线程来进行繁重的工作,而使用一个线程来遍历树。

我不认为它会在任何时候停止工作,但是使用多线程会增加CPU使用率,因为计算机一次执行更多操作,因此不使用多线程而只是使用多线程可能更安全但更慢。以下:

static void Traverse<T>(Tree<T> node, Action<T> action)
{
 if (node == null) return;
 action(node);
 Traverse(node.Left, action);
 Traverse(node.Right, action);
}

不过,这会比较慢,因此,如果您担心它的运行速度,您将希望使用原始版本。

暂无
暂无

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

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