Sto utilizzando jsr166y ForkJoinPool per distribuire attività di calcolo tra i thread. Ma chiaramente devo fare qualcosa di sbagliato.ForkJoinPool parallelism = 1 deadlock
Le mie attività sembrano funzionare in modo impeccabile se creo ForkJoinPool con parallelismo> 1 (il valore predefinito è Runtime.availableProcessors(); ho eseguito con 2-8 thread). Ma se creo ForkJoinPool con parallelism = 1, vedo deadlock dopo un numero imprevedibile di iterazioni.
Sì - il parallelismo di impostazione = 1 è una pratica strana. In questo caso sto analizzando un algoritmo parallelo con l'aumentare del numero di thread e voglio confrontare la versione parallela, eseguita con un singolo thread, con un'implementazione seriale di base, in modo da accertare con precisione il sovraccarico dell'implementazione parallela .
Di seguito è riportato un semplice esempio che illustra il problema che sto vedendo. Il 'compito' è una falsa iterazione su un array fisso, suddivisa in modo ricorsivo in 16 sottoattività.
Se eseguito con THREADS = 2 (o più), viene eseguito in modo affidabile fino al completamento, ma se eseguito con THREADS = 1, si verifica invariabilmente deadlock. Dopo un numero imprevedibile di iterazioni, il ciclo principale si blocca in ForkJoinPool.invoke(), in attesa su task.join() e il thread di lavoro termina.
Io corro con JDK 1.6.0_21 e 1.6.0_22 sotto Linux, e utilizzando una versione di jsr166y scaricato un paio di giorni fa dal sito di Doug Lea (http://gee.cs.oswego.edu/dl/concurrency-interest/index.html)
Eventuali suggerimenti per quello che mi manca ? Molte grazie in anticipo.
package concurrent;
import jsr166y.ForkJoinPool;
import jsr166y.RecursiveAction;
public class TestFjDeadlock {
private final static int[] intArray = new int[256 * 1024];
private final static float[] floatArray = new float[256 * 1024];
private final static int THREADS = 1;
private final static int TASKS = 16;
private final static int ITERATIONS = 10000;
public static void main(String[] args) {
// Initialize the array
for (int i = 0; i < intArray.length; i++) {
intArray[i] = i;
}
ForkJoinPool pool = new ForkJoinPool(THREADS);
// Run through ITERATIONS loops, subdividing the iteration into TASKS F-J subtasks
for (int i = 0; i < ITERATIONS; i++) {
pool.invoke(new RecursiveIterate(0, intArray.length));
}
pool.shutdown();
}
private static class RecursiveIterate extends RecursiveAction {
final int start;
final int end;
public RecursiveIterate(final int start, final int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if ((end - start) <= (intArray.length/TASKS)) {
// We've reached the subdivision limit - iterate over the arrays
for (int i = start; i < end; i += 3) {
floatArray[i] += i + intArray[i];
}
} else {
// Subdivide and start new tasks
final int mid = (start + end) >>> 1;
invokeAll(new RecursiveIterate(start, mid), new RecursiveIterate(mid, end));
}
}
}
}
Sembra che funzioni come progettato. Si richiede il parallelismo di 1, ma in questo caso si aggiungono due attività in invokeAll. Ma non sono un esperto in questo, quindi potrei sbagliarmi. –
L'ho già sentito da altre persone, che l'impostazione del numero di thread su "one more" risolve le cose. –
Re: Jochen - Come ho capito il framework, dovremmo essere in grado di aggiungere un numero arbitrario di task, indipendentemente dal livello di parallelismo (thread count). Ad esempio, potremmo suddividere ricorsivamente un'attività di grandi dimensioni in 256 piccoli task separati, ma dovremmo essere in grado di eseguire quell'algoritmo su una macchina con meno di 256 processori. Inoltre, il deadlock non è immediato (come ci si aspetterebbe se 2 thread di operazioni/1 fosse illegale - invece è dopo un numero imprevedibile di iterazioni, ma sono relativamente nuovo anche a FJ, quindi potrei essere frainteso – AaronD