【问题标题】:Multithreading and recursion together多线程和递归在一起
【发布时间】:2011-07-27 21:46:54
【问题描述】:

我有以深度优先方式处理树结构的递归代码。代码基本上是这样的:

function(TreeNode curr) 
{
    if (curr.children != null && !curr.children.isEmpty()) 
    {
        for (TreeNode n : curr.children) 
    {
            //do some stuff
            function(n);
        }
    } 
    else 
    {
        //do some other processing
    }
}

我想使用线程来更快地完成这项工作。大部分时间都花在遍历上,所以我不想只创建一个线程来处理“其他处理”,因为它不需要那么长时间。我想我想在“做一些事情”时分叉线程,但这将如何工作?

【问题讨论】:

  • 所以处理顺序不重要?孩子们可能会以随机顺序获得进程?

标签: java multithreading recursion parallel-processing


【解决方案1】:

Fork/Join framework 是一个很好的例子,它将被包含在 Java 7 中。作为用于 Java 6 的独立库,可以下载here

类似这样的:

public class TreeTask extends RecursiveAction {
    private final TreeNode node;
    private final int level;

    public TreeTask(TreeNode node, int level) {
        this.node = node;
        this.level = leve;
    }

    public void compute() {
        // It makes sense to switch to single-threaded execution after some threshold
        if (level > THRESHOLD) function(node);

        if (node.children != null && !node.children.isEmpty()) {
            List<TreeTask> subtasks = new ArrayList<TreeTask>(node.children.size());
            for (TreeNode n : node.children) {
                // do some stuff
                subtasks.add(new TreeTask(n, level + 1));
            }
            invokeAll(subtasks); // Invoke and wait for completion
        } else {
            //do some other processing
        }
    }
}

...
ForkJoinPool p = new ForkJoinPool(N_THREADS);
p.invoke(root, 0);

fork/join 框架的关键点是工作窃取——在等待子任务完成的同时线程执行其他任务。它允许您以直接的方式编写算法,同时避免线程耗尽的问题,就像使用 ExecutorService 的幼稚方法一样。

【讨论】:

  • 我已经更新了我的帖子,我肯定想使用这个 fork 框架,但不确定它如何适应
  • 最后一行必须是p.invoke(new TreeTask(root, 0));
  • 请注意,如果子任务的创建速度比工作线程执行的速度快,提交队列将溢出,您会收到 RejectedExecutionException("Queue capacity exceeded")。我正在寻找解决该问题的方法...
【解决方案2】:

在您在单个节点上工作的// do some stuff 代码块中,您可以做的是将节点提交给某种ExecutorService(以Runnable 的形式在节点上工作) .

您可以将ExecutorService 配置为由一定数量的线程池支持,允许您解耦“处理”逻辑(以及围绕创建线程、创建多少线程等的逻辑)来自您的树解析逻辑。

【讨论】:

  • 所以在这种情况下,我不会使用多个线程遍历树。我用一个线程遍历树?
  • "ExecutorService,您曾经由 一定数量的线程组成的池支持"嗯...我不会那样做。由于我们在任务之间存在依赖关系,因此可能会导致死锁。或者你必须非常注意提交的订单任务。
  • 是的。您试图使这项工作的哪一部分同时进行 - 解析树(它只是遍历内存中已经存在的数据结构)或对它的工作?如果是前者,那么这可能没有太大帮助。
  • @pavelrappo 问题在哪里表明这里的任务之间存在依赖关系?我只是根据问题中的内容来回答。
  • 我想我并没有真正考虑过我想让哪个部分并发。似乎进行具有一定处理量的多线程遍历是相当普遍的事情,我认为可能有一种标准的方法来做到这一点,如果你愿意的话,这是一个最佳实践
【解决方案3】:

这个解决方案假设处理只发生在叶节点上,并且树的实际递归不需要很长时间。

我会让调用者线程进行递归,然后由 BlockingQueue 的工作人员通过线程池处理叶子。我这里有几个地方没有处理InterruptedException

public void processTree(TreeNode top) {
    final LinkedBlockingQueue<Runnable> queue =
        new LinkedBlockingQueue<Runnable>(MAX_NUM_QUEUED);
    // create a pool that starts at 1 threads and grows to MAX_NUM_THREADS
    ExecutorService pool =
        new ThreadPoolExecutor(1, MAX_NUM_THREADS, 0L, TimeUnit.MILLISECONDS, queue,
            new RejectedExecutionHandler() {
                public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                    queue.put(r);  // block if we run out of space in the pool
                }
            });
    walkTree(top, pool);
    pool.shutdown();
    // i think this will join with all of the threads
    pool.awaitTermination(WAIT_TILL_CHILDREN_FINISH_MILLIS, TimeUnit.MILLISECONDS);
}
private void walkTree(final TreeNode curr, ExecutorService pool) {
    if (curr.children == null || curr.children.isEmpty()) {
        pool.submit(new Runnable() {
            public void run() {
                processLeaf(curr);
            }
        });
        return;
    }
    for (TreeNode child : curr.children) {
        walkTree(child, pool);
    }
}
private void processLeaf(TreeNode leaf) {
    // ...
}

【讨论】:

  • 遍历其实是瓶颈。在节点上完成的工作其实并没有那么难
  • 嗯。那很有意思。除非它真的是一棵怪物树,否则考虑到内存争用,我不确定线程​​是否会产生很大的不同。
  • 由于 java-ee 不支持 ForkJoinPool 我正在考虑采用您的方法。关于这段代码我不清楚的一件事是——“只有一个递归线程”?这个限制在哪里?
  • walkTree(...) 方法由同样调用processTree(...) 的线程运行。那是为叶处理@donlys 提交作业的线程。我将编辑我的答案。
  • 我在 java-ee 中使用这种方法时遇到了一个障碍:我不能使用 pool.shutdown() 或 pool.awaitTermination。
猜你喜欢
  • 2011-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多