2015-05-05 51 views
21

我想通過一個方法提交Runnable任務到ForkJoinPool:帶有非遞歸任務的Java ForkJoinPool,是否可以竊取工作?

forkJoinPool.submit(Runnable task) 

請注意,我用的JDK 7

引擎蓋下,他們轉變成ForkJoinTask對象。 我知道ForkJoinPool是有效的,當一個任務被遞歸分割成小的時候。

問:

是否工作竊取仍處於ForkJoinPool工作,如果沒有遞歸?

在這種情況下值得嗎?

更新1: 任務很小,可能不平衡。即使是嚴格相等的任務,諸如上下文切換,線程調度,停車,頁面丟失等等也會導致不平衡

更新2: Doug Lea的該Concurrency JSR-166 Interest組中寫道,通過給這個一個提示:

這也大大提高了產量,當所有任務異步提交到池 而不是分叉,這將成爲一種合理的 構造actor框架的方式,以及許多其他可能使用ThreadPoolExecutor的普通服務。

我認爲,當談到合理的小型CPU限制任務時,由於這種優化,ForkJoinPool是最佳選擇。重點是這些任務已經很小,不需要遞歸分解。 工作偷竊工作,無論是大型還是小型任務 - 任務可以由另一名自由工人從繁忙工人的Deque尾巴抓取。

更新3: Scalability of ForkJoinPool - 由阿卡隊乒乓的基準顯示出巨大的效果。

儘管如此,要更有效地應用ForkJoinPool,還需要進行性能調整。

+0

我已經想過這個我自己,我想這是你提到的線程的停車和鎖定。這就是爲什麼我認爲ring buffers(disruptor)比unwrapped fork join更快(至少對於我的非遞歸事件任務)。 –

+0

@Adam Gent我們也使用了Disruptor - 它速度驚人。是的,RingBuffer的想法應用於許多現代數據結構中。但是,它們在使用情況上有所不同:1. Disruptor使用1個線程1用戶模型進行流水線操作,但無法竊取工作,而2. FJ是一羣工作人員 - 工作分佈在多個線程(m:n)中。 –

+0

我很熟悉fj-rb的差異,但我認爲他們都可能會減少鎖定,但我不是專家。 –

回答

13

ForkJoinPool源代碼有一個很好的部分叫做「實現概述」,閱讀最終的真相。下面的解釋是我對JDK 8u40的理解。

從第一天開始,ForkJoinPool每個工作線程都有一個工作隊列(我們稱之爲「工作隊列」)。分叉的任務被壓入本地工作隊列中,準備再次被工作人員彈出並執行 - 換句話說,它看起來像工作線程視圖中的堆棧。當一名工作人員耗盡工人隊伍時,它會四處走動並試圖從其他工人隊列中竊取任務。那就是「偷工減料」

現在,在(IIRC)JDK 7u12,ForkJoinPool之前,有一個全局的提交隊列。當工作線程用完本地任務以及要竊取的任務時,他們到達那裏並試圖查看是否有外部工作可用。在這種設計中,對於經常由ArrayBlockingQueue支持的例如ThreadPoolExecutor沒有優勢。

之後它發生了顯着變化。在提交隊列被確定爲嚴重的性能瓶頸之後,Doug Lea等人也提交了提交隊列。事後看來,這是一個明顯的想法:您可以重用可用於工作隊列的大部分機制。你甚至可以鬆散地爲每個工作者線程分配這些提交隊列。現在,外部提交進入提交隊列之一。然後,沒有工作的工作人員可以先查看與特定工作人員相關的提交隊列,然後四處尋找其他人的提交隊列。也可以撥打「偷工減料」。

我看到很多受益於此的工作負載。即使對於簡單的非遞歸任務,這種特殊的設計優勢ForkJoinPool早已得到了認可。 concurrency-interest @的許多用戶要求提供一個簡單的盜取工作的執行程序,而不需要所有的祕密。這是爲什麼我們在JDK 8中有Executors.newWorkStealingPool()以後的原因之一 - 目前正在委託給ForkJoinPool,但爲了提供更簡單的實現而打開。