2011-04-01 63 views
6

具有在深度優先方式處理的樹結構遞歸代碼。該代碼基本上是這樣的:多線程和遞歸我一起

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 
    } 
} 

我想使用線程,使這個完整的速度更快。大多數時間都是遍歷的,所以我不想創建一個線程來處理「其他處理」,因爲它不需要那麼長時間。我認爲我想在「做一些事情」方面進行討論,但這會如何工作?

+1

所以處理的順序並不重要?孩子們可能會以隨機順序獲得流程? – 2014-01-18 11:59:54

回答

12

對於要包含在Java 7中的Fork/Join framework這是一個很好的例子。作爲與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); 

叉的關鍵點/ join框架的工作竊取 - 在等待子任務線程完成執行其他任務。它可以讓你以直接的方式編寫算法,同時避免線程耗盡的問題,因爲它可以與ExecutorService一樣天真的應用程序。

+0

我已經更新了我的帖子,我一定要使用這個叉子框架,但不知道如何它 – JPC 2011-04-01 19:50:17

+1

最後一行符合一定爲p.invoke(新TreeTask(根,0));' – eXXXXXXXXXXX2 2012-09-29 13:57:55

+0

注意的是,如果創建子任務比工作線程執行速度更快,提交隊列將溢出,並且會出現_RejectedExecutionException(「超出隊列容量」)_。我正在尋找一個解決方案,該問題... – 2015-10-04 00:28:13

3

// do some stuff代碼塊中,您可以在單個節點上工作,您可以改爲將節點提交給某種ExecutorService(形式爲Runnable,它將在節點上工作)。

您可以配置ExecutorService,您使用該ExecutorService由一定數量的線程池支持,從而允許從「處理」邏輯(連同創建線程的邏輯,創建的數量等)中分離出來你的樹分析邏輯。

+0

所以在這種情況下,我沒有遍歷樹使用多個線程。我使用一個線程遍歷樹? – JPC 2011-04-01 18:51:44

+0

「您使用由*一定數量的線程池來支持'ExecutorService' *」唔...我不會那麼做的。由於我們有任務之間的依賴關係,它可能會導致死鎖。或者你必須非常注意提交任務的順序。 – 2011-04-01 18:53:57

+0

是的。這項工作的哪一部分是你試圖進行併發 - 解析樹(它只是簡單地遍歷已經存在於內存中的數據結構)或者它的工作?如果前者,那麼這可能沒有太大的幫助。 – 2011-04-01 18:54:45

2

該解決方案假定處理只發生在葉節點和樹的實際遞歸併不需要很長的時間。

我會調用程序線程做遞歸,然後這個過程通過一個線程池的葉子工人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) { 
    // ... 
} 
+0

遍歷實際上是瓶頸。在節點上完成的工作其實並不困難 – JPC 2011-04-02 16:05:00

+0

呵呵。那很有意思。除非它真的是一個怪物樹,否則考慮到內存爭用,我不確定線程​​是否會產生一大堆差異。 – Gray 2011-04-02 16:34:37

+0

因爲java-ee不支持ForkJoinPool我正在考慮採用你的方法。有一件事我不清楚這個代碼是 - 「只有一個遞歸線程」?限制在哪裏? – donlys 2017-01-11 16:08:04