Creare un codice indipendente ed immutabile per far lavorare in multithreading un ciclo for parallelizzabile

di il
47 risposte

47 Risposte - Pagina 4

  • Re: Creare un codice indipendente ed immutabile per far lavorare in multithreading un ciclo for parallelizzabile

    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 ...
  • Re: Creare un codice indipendente ed immutabile per far lavorare in multithreading un ciclo for parallelizzabile

    Sempre grazie per la tua disponibilità, come al solito sei sempre chiarissimo. Solo una piccola precisazione se non mi spari anche se ne avresti tutte le ragioni!
    1) Anche compute() è sincrono, giusto?
    2) Se rightTask impiega più tempo di leftTask compute() blocca il thread corrente aspettando che rightTask finisca. Al termine di rightTask join() unisce i risultati di leftTask a rightTask, giusto?
    3) Se rightTask impiega meno tempo di leftTask, quando si arriva a join() il metodo attende che leftTask finisca e al termine di leftTask join() unisce i risultati di leftTask a rightTask, giusto?

    No da solo non sono in grado di scrivere questa modifica neppure in 1000 anni perché non so come rendere sincronizzata la lista. Ci deve essere qualche metodo o classe aggiuntiva perché reentrantReadWriteLock non è sufficiente.

    Oltre a Java mi compro anche un libro sul DB ed uno su JS perché Spring Boot è anche REST. Per creare un'applicazione REST a mio avviso serve qualche nozione in più di JS.

    Non sai il desiderio che ho di terminare questa esercitazione..., non credere...
  • Re: Creare un codice indipendente ed immutabile per far lavorare in multithreading un ciclo for parallelizzabile

    iBaffiPro ha scritto:


    se non mi spari anche se ne avresti tutte le ragioni!
    No no, non sparo.

    iBaffiPro ha scritto:


    1) Anche compute() è sincrono, giusto?
    Allora: in Java tutti (ripeto TUTTI) i metodi sono "sincroni", non esiste il concetto di metodo "asincrono".
    Il fatto che un metodo come il fork() di ForkJoinTask faccia eseguire quel task "asincrono" è solo una questione architetturale interna. Il fork() semplicemente fa un "push" del task (sé stesso) sulla coda del pool di thread del F/J e basta, poi ritorna subito. Il fork() quindi è sincrono come tutti i metodi, semplicemente il suo "lavoro" è passare un task ad un pool di thread e questo di per sé è veloce.

    iBaffiPro ha scritto:


    2) Se rightTask impiega più tempo di leftTask compute() blocca il thread corrente aspettando che rightTask finisca. Al termine di rightTask join() unisce i risultati di leftTask a rightTask, giusto?
    3) Se rightTask impiega meno tempo di leftTask, quando si arriva a join() il metodo attende che leftTask finisca e al termine di leftTask join() unisce i risultati di leftTask a rightTask, giusto?
    join() semplicemente attende (se/quanto necessario) la terminazione del task. Stop, tutto lì. E restituisce anche il risultato, naturalmente, dato che se join() ritorna, il task è terminato fornendo in output un valore.

    Se il leftTask finisce prima di rightTask, vuol semplicemente dire che il leftTask.join() sarà immediato. Se invece il leftTask impiega di più del rightTask, allora leftTask.join() starà un po' bloccato in attesa della fine.

    iBaffiPro ha scritto:


    No da solo non sono in grado di scrivere questa modifica neppure in 1000 anni perché non so come rendere sincronizzata la lista. Ci deve essere qualche metodo o classe aggiuntiva perché reentrantReadWriteLock non è sufficiente.
    Se facessi quello che ho ipotizzato io, una classe apposita per il Fork/Join pool che splitta su gruppi di colonne, magari un po' flessibile potendo passare il supplier, combiner ecc.. dall'esterno, NON ci sarebbe alcun bisogno di sincronizzazione esplicita.
    Nessun synchronized, nessun ReentrantReadWriteLock, nessuna collezione speciale tipo CopyOnWriteArrayList ecc..
    Semplicemente si sfrutta il principio del "divide-et-impera" del Fork/Join pool.

    Come ho detto, io in mezz'oretta lo scriverei senza problemi ....

    iBaffiPro ha scritto:


    Oltre a Java mi compro anche un libro sul DB
    Prima un libro solo su Java.

    iBaffiPro ha scritto:


    ed uno su JS perché Spring Boot è anche REST. Per creare un'applicazione REST a mio avviso serve qualche nozione in più di JS.
    Javascript in quest'ottica è solo per la parte front-end (se il back-end è Java e non usi altro lato server tipo Node.js). Ma REST comunque è ancora un altro discorso. Dimenticalo ancora per un po' ...

    iBaffiPro ha scritto:


    Non sai il desiderio che ho di terminare questa esercitazione..., non credere...
    Anche io ....
Devi accedere o registrarti per scrivere nel forum
47 risposte