Newsgroup: it.comp.java
Titolo: multi threads pool in cascata sequenziale

Salve a tutti,
sto scrivendo un parser (utilizzando la libreria HTMLParser) e vorrei
sfruttare l'esecuzione multi-threads.
Posto un po' di codice funzionante: (compila solo se aggiungete la
libreria suddetta)
// Test006.java
package test006;
/**
 * JDK 1.6.0_16-b01 ~ created with Eclipse "Galileo"
 * user: Giordano "mejoro" Fidilio
 * mail: 
dnaofw...@gmail.com
 * date: 07/nov/2009   00.04.52
 */
/*import org.htmlparser.Parser;
import org.htmlparser.lexer.Lexer;
import org.htmlparser.util.NodeList;
import org.htmlparser.Node;
import org.htmlparser.util.ParserException;
*/
import org.htmlparser.util.NodeList;    import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.io.File;
import java.io.FileNotFoundException;
import org.htmlparser.tags.ImageTag;
public class Test006 {
    // CAMPI
 // private /*List<NodeList>*/List<Future<NodeList>>
allHtmlPagesParsed;
   /* caricare tutti gli url...
    * ...ala fine l'operazione compiuta su ogni url genererà un
arraylist di arraylist
    * di file dove il primo file è sempre il testo e poi ci sono tutte
le altre risorse
    * scaricate da quell'url.
    *
    * Per ogni url creare un Parser (si connete una volta sola per
quel solo url ?)
    * parsare il contenuto web
    * settare il lexer del parser a zero
    * parsare tutti i tag IMG
    * prendere solo i tag univoci
    * scaricare quelle immagini
    * copiarle in una cartella "nome della pagina appena parsata_file"
    *
    * in caso di callable ritornare (a chi? al main??) un arraylist di
arraylist<file>
    * dove il primo file, indice zero, è sempre il testo della pagina
e a seguire ci
    * sono tutti gli altri files (immagini, ...) parsati
successivamente ad esso.
    *
    * è stata caricata una classe incaricata delle correzzioni? Se si
allora eseguire
    * le correzzioni di quella classe per ogni files testo (e altri
files) che
    * "contratta" quella tipologia di correzzioni (le correzzioni sono
una interfaccia?)
    */
    // COSTRUTTORE
    public Test006 () {
    List<Future<NodeList>> allHtmlPagesParsed = null;
    String[] url = readURLsFile();
    int maxPoolDimension = 10;
    int num = url.length;
    ExecutorService poolExecutor = Executors.newFixedThreadPool
(maxPoolDimension);
    ArrayList<ExecAdd> execution = new ArrayList<ExecAdd>(); //
List<Future<NodeList>> allResults = null;
    // Create an instance of the calculators
    // to be executed.
    for (int index = 0; index < num; index++)
    {
        execution.add( new ExecAdd(index, url[index]) );
    }
	// To execute the threads, the list of the threads are given to the
thread pool.
    try {
        // Perform all the pool threads executions.
        allHtmlPagesParsed = poolExecutor.invokeAll(execution);
        // Output the results.
        // When the get() method is called, the system will wait for
the thread to
        // finish execution if it has not finished already and then
return the result.
        // All that is left is the handling of the exceptions that can
possibly be
        // thrown.
        for (Future<NodeList> htmlParsed : allHtmlPagesParsed) //
allResults)
        {
        System.out.println(" -> ");
            String htmlSourceCode = htmlParsed.get().toHtml();
            System.out.println(htmlSourceCode);
        System.out.println(" /* --- */");
        }
    // When the get() method is called, the system will wait for the
thread to finish execution if it has not finished already and then
return the result.
    // All that is left is the handling of the exceptions that can
possibly be thrown.
    }
    catch (InterruptedException e) {
        System.err.println("Interrupted");
    }
    catch (ExecutionException e) {
        System.err.println("Execution exception");
    }
    finally {
        poolExecutor.shutdown();
    }
        for (ExecAdd executed : execution)
        {//List<ImageTag> imageTags = showImageTags
( executed.getNodeList() );
            for (ImageTag image : showImageTags( executed.getNodeList
() )  )
                System.out.println("IMG: " + image.getImageURL());
        }
    }
    // METODI
    public static void main (String[] args)
    {
    long start = System.currentTimeMillis();
    //
    	new Test006();
    //
    long end = System.currentTimeMillis();
    double time = (end - start) / 1000.0;
    System.out.printf("Spent time : %.1f seconds%n", time);
    }
    private String[] readURLsFile() {
    // TODO rimuovi, e avvisa, se ci sono link doppioni
    return  new String[]{
        "D:/skyler2.html"
        , "file:/C:/Programmi/EasyPHP5.3.0/www/skyler3.html"
        };
    }
    private List<ImageTag> showImageTags(NodeList $aPage) {
    List list = new ArrayList<ImageTag>();
        // MOSTRA I SOLI TAG <img> DELLA PAGINA
        for (org.htmlparser.Node n : $aPage
            .extractAllNodesThatMatch(
                new org.htmlparser.filters.TagNameFilter("IMG"), true
            )
            .toNodeArray()
        )
        list.add( (ImageTag)n );
    return list;
    }
}
//ExecAdd.java
package test006;
import java.io.FileNotFoundException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import org.htmlparser.Parser;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import java.net.URL;
public class ExecAdd implements Callable<NodeList>
{
	// CAMPI
    private int id = 0;
    private String url;
    private NodeList htmlPageParsed;
    // COSTRUTTORE
    public ExecAdd (int threadId, String $url)
 // throws java.net.MalformedURLException
    {
        id = threadId;
        url = $url;
    }
    // METODI
   /**
    * This is the method that will be executed
    * when the thread is running.
    */
    @Override
    public NodeList call () // throws Exception
    {
        //NodeList pageParsed = null;
        System.out.println("Thread " + id + " is parsing
…");
        try
        {
        Parser parser = new Parser(url);
        //NodeList list = parser.parse(null);
        return htmlPageParsed = parser.parse(null);
        }
        catch (ParserException pe)
        {
            Throwable throwable;
            throwable = pe.getThrowable ();
            if (null != throwable)
            {
                throwable = throwable.getCause ();
                if (throwable instanceof FileNotFoundException)
                    System.err.println ("broken link " +
((FileNotFoundException)throwable).getMessage () + " ignored");
                else
                    pe.printStackTrace ();
            }
            else
                pe.printStackTrace ();
        }
        // Delay for a visible amount of time.
        // This is just for show.
        /* try {
            Thread.sleep(2000);
        }
        catch (InterruptedException e) {
            System.err.println("Interrupted");
        } */
        return htmlPageParsed;
    }
    public NodeList getNodeList() {
    return htmlPageParsed;
    }
}
Praticamente per ogni URL da scaricare nasce un thread che lo parsa.
Se ci fossero 10 url ci sarebbero anche 10 threads concorrenti, ma può
succere che il thread0 completa il parsing dell'url0 in 20ms mentre il
thread8 impiega 400ms per parsare l'url8. Siccome il mio intento è
anche quello di manipolare il codice delle pagine parsate vorrei tanto
che non appena il thread0 ha completato il parsing subito avvia
un'altro thread che si oocuperà proprio di manipolare il testo
scaricato (poi, per farvi capire, intendo che questo secondo thread
invii il testo appena manipolato ad un terzo ThreadExecutor che si
incarica di scaricare tutte le immagini).
Non ho idea di come realizzare questa sequenzialità di thread.
Mi sapete consigliare ?
Infatti al momento attuale solo quando tutti i threads sono completati
allora in quel momento "parte" il blocco incaricato di mostrare il
codice sorgente delle pagine (...String htmlSourceCode = htmlParsed.get
().toHtml();) quindi se il thread8 non aveva ancora finito di parsare
rimaniamo sempre in attesa sua mentre io desidero che ogni url, appena
parsato, venga subito inviato all'operazione successiva...
...è una catena di montaggio.
Se sono stato poco chiaro chiedetemi pure e io vi saprò dire.
Scusate solo l'assenza di commenti nel codice, ma l'ho buttato giù nel
giro di appena 6 tentativi.
Grazie
Buon sababto

 Non ho idea di come realizzare questa sequenzialità di thread.
Domanda: che bisogno c'è di suddividere in due (o più) thread un
compito che andrà eseguito *sequenzialmente*? Se la risposta è
"nessuno" allora puoi accorpare parsing e processamento in un unico
thread :)
Certo per lo scaricamento delle immagini è giusto pensare ad un thread
pool, ma per il parsing e il processamento del testo... visto che son
fatti uno in fila all'altro, perché complicarsi la vita?
--
www.megadix.it


Si, certo... per parsare e trattare il testo non c'è bisogno di
multitasking (trattare il testo non è un'opereazione di IO così forte,
cosa che invece viene richiesto per salvare tutte le immagini) però
penso che se riesco a capire come suddividere ogni operazione
racchiudendola in suo task allora acquisisco il senso accdemico del
mio progetto, e quindi dopo non ho problemi ad applicarlo nei casi
reali.
Girando intorno al senso accedemico sto cercando di tenere un buon
livello di coesione(o accoppiamento?) delle varie funzionalità:
 - il main invoca il poolThreadExecutor
 - il pool fa partire il parsing di tutte le pagine
 - ogni pagina è ritornata al main
 - il main prende pgni pagina e la manda in input ad un altro pool
incaricato di manipolare (so che x qst operazione nn è richiesto il
multitasking)
 - il testo corretto viene ritornato al main
 - il main prende ogni testo corretto inviandolo ad un altro pool
incaricato di parsare le immagini (qui invece ci vuole e come!!)
 - le immagini sono "tornate" ala main
...
Come vedete la cosa è abbastanza stucchevole, ma proprio perchè
l'intento è quello di ottenere una precisa modularità, una sorta di
general purpose...
...poi il caso reale dell'applicazione conferirà alla stessa una sua
più precisa forma.
Quello che mi chiedo è:
ci vuole un pool per ognuno dei passaggi che intendiamo far eseguire ?
Cioè se abbiamo 10 url
avremo un primo pool di 10 solo per parsare, poi un secondo pool di 10
solo per trattare, un terzo sempre di 10 solo per parsare le
immagini...
se è questa la strada allora la cosa diventa un po' troppo
stucchevole, lo so, ma mi basta capire -per adesso- solo il senso
accademico: infatti il mio interessa principale non è tanto
programmare, ma capire COME programmare (saper pianificare,
immaginare, avere in mente un modello)
Grazie a tutti
e buona domenica


Scusa non avevo capito che era per studio :)
Comunque in ogni caso accorperei le prime due operazioni, ma per
quanto riguarda le immagini realizzerei un thread pool apposito,
utilizzato dal primo gruppo di thread, che si occupa di scaricare le
immagini.
Così quando (uno alla volta e in qualsiasi ordine), i primi finiscono
di scaricare e processare il file, possono subito utilizzare il
secondo thread pool per scaricare efficientemente le immagini, senza
aspettare.
--
www.megadix.it

 Scusate solo l'assenza di commenti nel codice, ma l'ho buttato giï¿½ nel


 > Scusate solo l'assenza di commenti nel codice, ma l'ho buttato giù nel


A sto punto mi sto chiedendo una cosa: esiste un qualche miglioramento
tra creare una classe Runnable per ogni diversa logica nella sequenza
(prima scarico il testo, poi lo correggo, poi scarico tutte le sue
immagini) oppure
mettendo tutte queste procedure insieme in un'unica sequenza dentro
una sola classe Runnable ottengo lo stesso risultato in termini di
funzionalità accettabile ?


 funzionalit� accettabile ?
Mah, la risposta a questo tipo di domande non � mai ovvia, perch� si 
mischiano piani diversi: ad esempio, per la mantenibilit� a lungo 
termine e l'estendibilit�, di solito piccoli moduli con uno scopo ben 
preciso semplificano la vita; d'altro canto, lo switch di thread 
penalizza dal punto di vista prestazionale. Qundi, a seconda della 
prospettiva con cui guardi, trovi risposte diverse.
Per il tuo caso specifico, ti faccio osservare che la sequenza � 
costituita da un'operazione I/O bound, da una CPU bound (+ o -) e da un 
gruppo di operazioni di nuovo I/O bound.
Secondo me ha senso dividerla come hai proposto inizialmente ha senso. 
Puoi usare molti thread per scaricare in parallelo le URL, considerando 
che il carico di ognuno sar� minimo perch� devono aspettare la rete, poi 
farli elaborare da un pool di manciata di thread (spannometricamente 
tanti quanti i core) con un carico quasi del 100%, e poi di nuovo da un 
pool nutrito di thread quasi disoccupati.
Ciao.

 che il carico di ognuno sarà minimo perché devono aspettare la rete, poi


 raffinare successivamente tutti questi switching.
Non penso sia una buona idea (a meno che non sia richiesto da progetto) 
n� creare thread da altri thread, n� in generale di creare thread a 
mano: usa un servizio Executor e coordina l'invio dei task da eseguire 
dal thread principale.
Come suggerisci, se fai qualche errore nel gestire l'accesso concorrente 
a sezioni protette, il debug diventa un incubo.
Ciao.

 né creare thread da altri thread, né in generale di creare thread a
