- Introduzione a al multi-threading in .Net -
 
COSA SERVE PER QUESTO TUTORIAL
Download | Chiedi sul FORUM | Glossario conoscenza base di VB .Net e dei concetti di processo e thread
Come funzionano i thread in VB .Net

HARDWARE, PROCESSI E THREAD
Cosa sono e perché sono importanti i thread

Da alcuni anni a questa parte lo sviluppo dei processori ha, per svariati motivi, imboccato la via dell'aumento del numero di core o processori più che delle prestazioni di un singolo di essi. Questo porta alcuni vantaggi e altri svantaggi; il principale problema è che la maggior parte dei programmi attualmente in circolazione è stato sviluppato per funzionare per un singolo processore, e di conseguenza ci si ritrova spesso nella frustrante situazione di avere un processore completamente utilizzato e un altro del tutto a riposo. Un uso consapevole e ben calibrato della tecnica del multi-threading può ovviare a questo problema. Ovviamente utilizzare più thread allo stesso tempo ha molti altri fini oltre a quello di sfruttare più core allo stesso tempo, ma, soprattutto oggi, è certamente importante sottolineare anche questo aspetto.
Ma cos'è un thread (o thread di esecuzione)? Come tutti sanno nella maggior parte dei casi in un sistema sono in esecuzione più processi contemporaneamente (un browser, un editor di testo e così via), ebbene, ognuno di questi processi è formato da uno o più thread che lavorano in parallelo (almeno virtualmente). I thread sono anche chiamati lightweight-process (processi leggeri) in quanto sono molto simili ad essi se non principalmente per il fatto che tutti i thread da cui è formato un processo condividono le stesse risorse (e sono per questo più leggeri dal punto di vista del carico del sistema), mentre i processi, nei moderni sistemi operativi, sono isolati.
Due thread dello stesso processo dunque condividono lo stesso address-space (spazio d'indirizzamento, ovvero hanno una parte di memoria in comune), segnali e file aperti mentre differiscono solo negli aspetti strettamente collegati all'esecuzione quali registri, program counter, stack e stato (in attesa, in esecuzione, pronto e così via); ma questi sono aspetti che non approfondiremo in questo articolo. Ciò che è importante comprendere è che in un processo possono esservi più thread, ovvero flussi di esecuzione paralleli, che permettono in sostanza di eseguire due operazioni contemporaneamente.
Un ultima precisazione prima di addentrarci ulteriormente nell'argomento: quando, parlando di multi-threading, si dice "contemporaneamente" o "in parallelo", si dovrebbero usare le virgolette in quanto, in realtà, si può avere un vero parallelismo solamente se sono presenti più core o processori, quindi si deve tenere ben presente che non sempre due thread verranno eseguiti realmente in contemporanea, ma nella maggior parte dei casi "in parallelo" significherà semplicemente che ogni thread utilizzerà il processore per un certo periodo di tempo, dopo il quale si interromperà per lasciare spazio ad un altro (come del resto accade anche per i processi).

THREADING IN .NET
Il namespace System.Threading e le classi per il multi-threading

In .Net ci si può servire della tecnica del multi-threading in due modi: creando direttamente un thread tramite la classe Thread (in System.Threading) oppure servendosi del ThreadPool (altra classe di System.Threading). Un thread andrebbe utilizzato per operazioni che richiedono un notevole quantitativo di tempo mentre un ThreadPool è adatto per eseguire in maniera asincrona compiti di breve durata. Ogni volta che si effettua una chiamata asincrona del tipo BeginRead, BeginInvoke e così via si sfrutta implicitamente il ThreadPool, ovvero un thread che esegue man mano una coda di operazioni. Si badi che il ThreadPool è utilizzato anche dal framework per eseguire operazioni interne asincrone ed è quindi bene non sovraccaricarlo di richieste di lunga durata.
Vediamo a questo punto un esempio che illustra come creare alcuni thread che si occupano di scaricare alcune pagine web (i cui URL sono memorizzati in un file).
Creare un thread è molto semplice:


Dim thrDownloader As New Thread(AddressOf DownloadURL)
thrDownloader.Start(strURL)

Si tratta semplicemente di istanziare la classe Thread passando come parametro l'indirizzo di una funzione da richiamare (tramite l'operatore AddressOf). Questa funzione deve essere una Sub la cui firma (ovvero tipo restituito e parametri accettati) corrisponda a quella di uno dei due delegati che il costruttore accetta, ovvero ThreadStart o ParametrizedThreadStart:


Public Delegate Sub ThreadStart()
Public Delegate Sub ParameterizedThreadStart(ByVal obj As Object)

Il primo è semplicemente una Sub che non riceve alcun parametro, mentre la seconda, come dice il nome, accetta un parametro Object che permette di comunicare con il thread appena creato. In questo caso noi ci siamo serviti di un ParameterizedThreadStart, come vedremo tra poco.
Una volta creato l'oggetto Thread per avviarlo non bisogna far altro che invocare il metodo Start, passando come parametro (se si utilizza un ParameterizedThreadStart) l'oggetto che deve ricevere il thread che si sta creando. In questo caso passiamo al thread strURL, stringa che contiene l'URL da scaricare.
Vediamo ora la Sub principale del nostro thread: DownloadURL.


Public Sub DownloadURL(ByVal URL As Object)
    Dim wrqRequest As Net.HttpWebRequest = CType(Net.HttpWebRequest.Create(URL.ToString()), _
        Net.HttpWebRequest)
    Dim wrsResponse As Net.HttpWebResponse
    Try
        wrsResponse = CType(wrqRequest.GetResponse(), Net.HttpWebResponse)
    Catch exc As Net.WebException
        wrsResponse = CType(exc.Response, Net.HttpWebResponse)
    End Try
    Console.WriteLine(String.Format("{0}: {1} - {2}", URL, wrsResponse.StatusCode.ToString("d"), _
        wrsResponse.StatusDescription))
    wrsResponse.Close()
End Sub

Si tratta di una semplice Sub che prende come parametro un URL, effettua una richiesta, stampa sulla console il responso dato dal server (anche in caso di errore) e infine termina la richiesta.

CONCLUSIONI E NOTE
Il bello deve ancora venire.

Se si testa l'applicazione più volte si potrà vedere che l'ordine dei risultati può variare di volta in volta a seconda dei tempi di risposta dei vari server, nonostante le richieste vengano eseguite sempre nello stesso ordine: questo ci fa capire che l'esecuzione è davvero parallela (asincrona) e non sequenziale (sincrona). Ovviamente era possibile ottenere lo stesso risultato senza creare alcun thread ma sfruttando le chiamate asincrone di HttpWebRequest (come BeginGetResponse, che implicitamente si servono del ThreadPool), ma abbiamo in questo modo reso l'idea di cosa può fare e come funziona un thread.
Dunque? Ci si potrebbe chiedere a questo punto dove sta la tanto acclamata difficoltà della programmazione multi-thread. Ebbene si noti che nel codice d'esempio i thread creati sono tra loro del tutto indipendenti, ovvero non necessitano di essere sincronizzati, né comunicano, né utilizzano variabili in comune, fatta eccezione per l'oggetto Console (che è utilizzabile da più thread senza problemi, ovvero è thread-safe, poiché espone per lo più metodi statici). Se così non fosse, se ad esempio si volesse avere accesso ad una variabile globale, bisognerebbe acquisire su di essa un accesso esclusivo onde evitare che altri thread la modifichino o si servano di una versione vecchia della stessa. Ma rimandiamo gli approfondimenti in proposito al prossimo articolo.

 

<< INDIETRO by VeNoM00