iBaffiPro ha scritto:
Forse il tuo codice non è adattabile a questo scenario però la dimensione della matrice è ininfluente.
No, NON è totalmente "ininfluente". Diciamo che
dipende sia dalla quantità di dati, sia da COSA si fa con i dati.
iBaffiPro ha scritto:
Il multi-threading si può applicare anche a matrici 10x10, su pochi dati possono essere fatti molti calcoli.
Se fai banali sommatorie, min/max, ecc.. queste sono "sciocchezze" e con una matrice di 10x10 valori il multi-threading è inutile, perché ci metterebbe ben di più di una soluzione "sequenziale" iterativa, per via del maggiore
overhead dovuto alla gestione e sincronizzazione dei thread.
Fai qualche benchmark per comprendere quanto sia fuorviante quello che stai dicendo. Se fosse come dici tu, tutti i programmatori Java si dovrebbero mettere ad usare parallelStream(), Fork/Join pool, thread-pool per qualunque cosa come se non ci fosse un domani .. giusto? E invece noooo.
iBaffiPro ha scritto:
Ho timore di non aver compreso bene il funzionamento di fork(), join() e compute(). Io ho capito questo:
1) fork(), join() sulla parte sinistra che viene trasferita su un nuovo thread;
2) compute() sulla parte destra della lista;
leftTask.fork(): obbliga il sistema operativo ad eseguire leftTask su un nuovo thread (thread 2);
leftTask.join(): riporta l'output di leftTask sul thread originario (thread 1);
rightTask.compute(): obbliga rightTask a partire sul thread originario (thread 1) altrimenti resterebbe fermo.
L'ordine perché è importante dato che parliamo di thread diversi?
Il fork() è asincrono, lancia il task in un altro thread del F/J pool ma poi ritorna
subito. Il join() invece è sincrono, bloccante, blocca il thread corrente per attendere la terminazione di quel task (restituendo anche il risultato).
Se fai come hai scritto tu ovvero:
MutateTask<T> leftTask = new MutateTask<>( ....... );
leftTask.fork();
leftTask.join();
MutateTask<T> rightTask = new MutateTask<>( ....... );
rightTask.compute();
Succede che tu lanci il leftTask ma poi facendo subito il join() blocchi il thread corrente per attendere la terminazione, "sprecando" quindi solo tempo. Il risultato è che PRIMA viene fatto tutto il leftTask, poi DOPO viene fatto tutto il rightTask. Insomma, sono in
sequenza, uno dietro l'altro, NON paralleli!!
Se invece fai come avevo scritto IO, cioè:
MutateTask<T> leftTask = new MutateTask<>( ....... );
leftTask.fork();
MutateTask<T> rightTask = new MutateTask<>( ....... );
rightTask.compute();
leftTask.join();
Qui succede che lancio il leftTask asincrono e poi non sto lì ad aspettarne la fine ma "furbescamente" sfrutto quel thread del pool in cui sono lì per eseguire
sincrono il rightTask. Quindi i due task sono PARALLELI. Nota che NON serve fare il fork()
anche del rightTask, perché altrimenti avresti 3 thread in ballo, di cui uno (quello "corrente") lo "sprecheresti" solo per stare lì in attesa dei due task asincroni. Quello in cui sei nel compute() è
già un thread del Fork/Join pool ...
sfruttalo, NON SPRECARLO!!
Questo è il motivo per cui l'ordine degli step nel compute() è importante.
Se non riesci a ragionare su queste cose, questa è ancora una volta la dimostrazione che non hai sufficienti nozioni su Java.
Tornando alla tua matrice, la cosa (l'unica al momento) che mi viene in mente è di fare una classe apposita per il F/J pool che sia specifica ed appropriata per la matrice, ovvero che "sappia" del List<List<Long>> e soprattutto che faccia lo splitting per gruppi di colonne (non per gruppi di righe che era l'unica cosa fattibile col mio ForkJoinListMutator).
Volendo farlo un po' "generico" e riutilizzabile anche per fare eventualmente logiche differenti, si potrebbe fare (ipotizzo solo per ora!) una classe ForkJoinMatrixOperator che sia usabile passando al metodo di elaborazione:
- un List<List<T>> (la matrice; T è il tipo dei valori da trattare, es. Long)
- un Supplier<R> che crea il contenitore nuovo per ciascun run del computeSequentially() (R è un qualunque risultato, es. List<Long> per i number da collezionare)
- un BinaryOperator<R> per "combinare" i risultati di 2 task left/right
- un MatrixCellOperator<R> (
functional interface) con un metodo che riceve riga/colonna/valore e il contenitore R (su cui la implementazione fa quello che vuole) e restituisce il nuovo (o vecchio) valore da assegnare nella cella.
Io se mi ci metto un attimo,
in mezz'oretta lo scrivo. Bene. E funzionante.
Non so se/quanto ci riusciresti tu da solo .....
Quindi: TERMINA questa esercitazione. Non ha importanza se non è ben fatta, se non ha risultati ottimali, se non è performante, se non ti soddisfa, ecc...
Non sono queste le "esercitazioni" Java che dovresti fare ....
Mi piacerebbe che arrivassero anche gli altri "bravi" del forum tipo @migliorabile, @oregon, @Alka e ti ribadissero anche loro le stesse cose che ti sto dicendo io ...