| 
		 - Multi-threading e data race: 
			monitor e SyncLock per l'accesso a dati condivisi - 
		 | 
		|||
| COSA SERVE PER QUESTO TUTORIAL | |||
| Download | Chiedi sul FORUM | Glossario | un server che supporti ASP .Net | ||
| Problematiche del multi-threading in .Net | |||
IL PROBLEMA Accesso a dati condivisi da più thread paralleli. Come detto nel precedente articolo i problemi con i thread sorgono quando si ha la necessità di accedere alla stessa risorsa (tipicamente una variabile) da due thread differenti in esecuzione parallela. Quando si scrive codice che deve leggere o scrivere su dati a cui potrebbe voler accedere anche un altro thread bisogna sempre tener presente che la possibilità che tra un'istruzione e l'altra il thread si interrompa e passi il controllo ad un altro. Questo secondo thread potrebbe alterare i dati su cui il primo stava lavorando creando comportamenti inattesi (tipicamente si verifica una data race, una corsa ai dati). Prendiamo un esempio classico: 
Imports System.Threading
Module Main
    Const intSomma As Integer = 100
    Private intTotale As Integer = 0
    Sub Main()
        Console.Write("Numero di thread da creare: ")
        Dim intThreadTotal As Integer = CInt(Console.ReadLine())
        Dim thrSommatori(intThreadTotal) As Thread
        'Creiamo il numero di thread specificato, tutti sulla funzione Somma
        For C1 As Integer = 0 To intThreadTotal - 1
            thrSommatori(C1) = New Thread(AddressOf Somma)
        Next C1
        'Avviamo tutti i thread
        For C1 As Integer = 0 To intThreadTotal - 1
            thrSommatori(C1).Start()
        Next C1
        'Attendiamo la terminazione di tutti i thread
        For C1 As Integer = 0 To intThreadTotal - 1
            thrSommatori(C1).Join()
        Next C1
        'Stampiamo il risultato ottenuto e quello previsto
        Console.WriteLine("Risultato: {0} (atteso: {1})", intTotale, intSomma * intThreadTotal)
        Console.ReadLine()
    End Sub
    'Funzione che effettua somme
    Public Sub Somma()
        For CSum As Integer = 1 To intSomma
            intTotale += 1
        Next CSum
    End Sub
End Module
		Questa semplice applicazione console non fa altro che creare un numero di thread specificato dall'utente, avviarli e attendere la loro terminazione. I thread incrementano di 1 100 volte una variabile globale. Se si prova ad eseguire il programma specificando un numero di thread sufficientemente elevato (provare con 10 e poi a salire di ordini di grandezza) si vedrà che il risultato finale non sarà quello atteso: ad esempio sulla macchina su cui è stato testato, con 1000 thread, il risultato finale era 99'800 invece che 100'000. Perché questo strano comportamento? Analizziamo la riga che crea il problema: intTotale +=1 Niente di più semplice apparentemente, ma per capire bene dove nasce il problema dobbiamo entrare in profondità in questa istruzione: per prima cosa legge la variabile intTotale, effettua la somma e infine scrive il risultato in intTotale. È quindi equivalente al seguente codice: Dim intTemp As Integer = intTotale intTemp = intTemp + 1 intTotale = intTemp 
		L'errore si verifica per via del fatto che nel tempo che trascorre tra 
		la lettura di intTotale e la sua riscrittura in memoria, un altro thread ha 
		incrementato quella stessa variabile, ma di questo fatto il thread che era in 
		esecuzione precedentemente non può essersi accorto e quindi l'incremento 
		del secondo thread viene perso con la scrittura del nuovo valore. Come forzare la scrittura e lettura di una variabile dalla memoria principale e assicurarsi di avervi accesso esclusivo. 
		Per ovviare a questo problema ci si può servire dei monitor. In .Net ad 
		ogni oggetto è associato (in teoria) un monitor, che può essere 
		acquisito, 
		così impedendo l'accesso a quell'oggetto da parte, e rilasciato, rendendo 
		di nuovo disponibile l'oggetto ad altri thread. Acquisire il monitor 
		(altresì detto lock) su una variabile significa quindi 
		assicurarsi che fino a quando non lo si rilascerà, i dati della 
		variabile non saranno 
		utilizzati da nessun altro thread (che verrà quindi messo in stato di 
		attesa fino al rilascio). Per acquisire un lock ci si server di Monitor.Enter
		e per rilasciarlo di Monitor.Exit. Si noti inoltre che una 
		chiamata a Monitor.Enter sortisce anche l'effetto di forzare una 
		lettura dalla memoria principale (una lettura che si definisce 
		volatile) e viceversa Monitor.Exit forza una scrittura sulla 
		memoria principale (scrittura che si definisce volatile), in modo 
		da non avere versioni diverse della stessa variabile nei vari livelli di 
		memoria. Private objMonitor As New Object() Modifichiamo quindi la routine Somma: 
Public Sub Somma()
    For CSum As Integer = 1 To intSomma
        Monitor.Enter(objMonitor)
        intTotale += 1
        Monitor.Exit(objMonitor)
    Next CSum
End Sub
		Se ora riproviamo ad eseguire il codice, otterremo il risultato sperato, ovvero nessun incremento è stato perso. CONSIDERAZIONI FINALIMonitor, eccezioni e la parola chiave SyncLock Per imporre un lock su una variabile, Visual Basic, mette a disposizione anche un metodo più pulito, sicuro ed elegante rispetto a Monitor.Enter e Monitor.Exit, ovvero la parola chiave SyncLock. SyncLock definisce un blocco (simile a Using) che fa le veci delle chiamate a Monitor.Enter e Monito.Exit, e inoltre concepisce anche il caso in cui si generi un'eccezione. Infatti, se per qualche ragione l'incremento dovesse generare un'eccezione (ad esempio di overflow) il lock non verrebbe mai rilasciato nella versione del codice precedente, il che comporterebbe che tutti i thread in attesa per la disponibilità di objMonitor rimarrebbero inesorabilmente bloccati per sempre. Dovremmo dunque scrivere: 
Public Sub Somma()
    For CSum As Integer = 1 To intSomma
        Monitor.Enter(objMonitor)
        Try
            intTotale += 1
        Finally
            Monitor.Exit(objMonitor)
        End Try
    Next CSum
End Sub
		Che è del tutto equivalente alla seguente sintassi: 
Public Sub Somma()
    For CSum As Integer = 1 To intSomma
        SyncLock objMonitor
            intTotale += 1
        End SyncLock
    Next CSum
End Sub
		
  | 
	|||
| << INDIETRO | by VeNoM00 | ||