2015-05-05 18 views
21

Voglio inviare Runnable attività in ForkJoinPool attraverso un metodo:Java ForkJoinPool con attività non ricorsive, funziona il furto del lavoro?

forkJoinPool.submit(Runnable task) 

nota, io uso JDK 7.

Sotto il cofano, si trasformano in oggetti ForkJoinTask. So che ForkJoinPool è efficiente quando un'attività viene suddivisa in più piccole in modo ricorsivo.

Domanda:

funziona rubare ancora lavorano nel ForkJoinPool se non c'è ricorsione?

Ne vale la pena in questo caso?

Aggiornamento 1: Le attività sono piccole e possono essere sbilanciate. Anche per compiti strettamente uguali, cose come il cambio di contesto, la programmazione dei thread, il parcheggio, le mancate pagine ecc. Si mettono in mezzo allo squilibrio .

Aggiornamento 2: Doug Lea ha scritto nel gruppo Concurrency JSR-166 Interest, dando un suggerimento su questo:

Questo migliora notevolmente il throughput quando tutte le attività sono asincrone e sottoposti alla piscina piuttosto che biforcuta , che diventa un ragionevole metodo per strutturare le strutture degli attori, così come molti servizi semplici che è possibile utilizzare in alternativa a ThreadPoolExecutor per .

presumo, quando si tratta di ragionevolmente piccole attività della CPU-bound, ForkJoinPool è la strada da percorrere, grazie a questa ottimizzazione. Il punto principale è che questi compiti sono già piccoli e non richiedono una scomposizione ricorsiva. Il furto del lavoro funziona, indipendentemente dal fatto che si tratti di un'attività grande o piccola: le attività possono essere prese da un altro lavoratore libero dalla coda di un lavoratore occupato.

Update 3: Scalability of ForkJoinPool - analisi comparativa per Akka team di ping-pong mostra grandi risultati.

Nonostante ciò, per applicare ForkJoinPool in modo più efficiente è necessario ottimizzare le prestazioni.

+0

Mi sono chiesto questo e penso che sia quello che hai menzionato con il parcheggio e il blocco dei fili. Questo è il motivo per cui ritengo che i buffer dell'anello (disruptor) siano persino più veloci di un fork fork non eseguito (almeno per le attività di evento non ricorsive). –

+0

@Adam Gent Abbiamo usato anche Disruptor - È incredibilmente veloce. E sì, l'idea di RingBuffer è applicata in molte moderne strutture di dati. Tuttavia, differiscono nei casi d'uso: 1. un Disruptor utilizza 1 thread - 1 modello consumer per il pipelining e non può rubare il lavoro, mentre 2. FJ è un pool di worker - il lavoro è distribuito su un numero di thread (m: n). –

+0

Ho familiarità con le differenze di fj - rb ma penso che entrambi potrebbero essere di benedizione di meno blocco, ma non sono un esperto in alcun modo. –

risposta

13

ForkJoinPool codice sorgente ha una bella sezione chiamata "Panoramica sull'implementazione", letto per una verità assoluta. La spiegazione che segue è la mia comprensione per JDK 8u40.

Dal primo giorno, ForkJoinPool aveva una coda di lavoro per thread di lavoro (chiamiamoli "code di lavoro"). Le attività bifronte vengono inserite nella coda di lavoro locale, pronte per essere nuovamente estratte dal worker e eseguite; in altre parole, assomigliano a una pila dalla prospettiva del thread di lavoro. Quando un lavoratore esaurisce la sua coda di lavoro, va in giro e prova a rubare le attività da altre code di lavoro. Questo è "lavoro che ruba".

Ora, prima (IIRC) JDK 7u12, ForkJoinPool aveva una singola coda di invio globale.Quando i thread di lavoro hanno esaurito le attività locali, così come i compiti da rubare, sono arrivati ​​e hanno cercato di vedere se il lavoro esterno è disponibile. In questo modello, non vi è alcun vantaggio rispetto a un normale, ad esempio, ThreadPoolExecutor supportato da ArrayBlockingQueue.

È cambiato in modo significativo dopo di allora. Dopo che questa coda di invio è stata identificata come il serio collo di bottiglia delle prestazioni, Doug Lea et al. barrato anche le code di invio. Con il senno di poi, è un'idea ovvia: puoi riutilizzare la maggior parte dei meccanismi disponibili per le code dei lavoratori. È anche possibile distribuire liberamente queste code di invio per thread di lavoro. Ora, l'invio esterno entra in una delle code di invio. Quindi, i lavoratori che non hanno lavoro da sgranocchiare, possono prima esaminare la coda di invio associata a un particolare lavoratore, quindi andare in giro a esaminare le code di invio degli altri. Si può chiamare che "ruba lavoro" anche.

Ho visto molti carichi di lavoro beneficiare di questo. Questo particolare vantaggio di design di ForkJoinPool anche per le normali attività non ricorsive è stato riconosciuto molto tempo fa. Molti utenti di interesse di concorrenza @ hanno chiesto un semplice esecutore di furto del lavoro senza tutto l'arcanico ForkJoinPool. Questo è uno dei motivi, per cui abbiamo Executors.newWorkStealingPool() in JDK 8 in poi - attualmente delegante a ForkJoinPool, ma aperto per fornire un'implementazione più semplice.

Problemi correlati