Pagina personale di:
Carlo Vecchio
appunti di C#, R, SQL Server, ASP.NET, algoritmi, numeri
Vai ai contenuti

C# - Thread e condivisione di variabili

C#

Condivisione di variabili
   •   Non condividere variabili tra più thread, perché il risultato non è quello aspettato
.
   •   Nell’esempio seguente, il Main() crea e avvia due thread: il primo è un “azzeratore” della variabile “contatore”; il secondo è un “incrementatore” della stessa variabile. Tutti i thread condividono la stessa variabile.

using System.Threading;

static
int contatore = 0;

static
void Main(string[] args)  
{  
   //Crea e avvia il primo thread
   Thread thread1 = new Thread(new ThreadStart(MyThreadMethod1));   
   thread1.Start();   

   //Crea e avvia il secondo thread
   Thread thread2 = new Thread(new ThreadStart(MyThreadMethod2));   
   thread2.Start();  

   // Il thread principale non fa niente
   Thread.Sleep(5000);  
   Console.ReadKey();  
}  

public static void MyThreadMethod1()  
{  
   for (int i = 0; i < 100; i++)  
   {  
       contatore = 0;  
       Console.WriteLine("Thread 1: Variabile --> " + contatore);  
   }   
}  

public static void MyThreadMethod2()  
{  
   for (int i = 0; i < 100; i++)  
   {  
       contatore++;  
       Console.WriteLine("Thread 2: Variabile --> " + contatore);  
   }  
}  


   •   L’output della console (che può essere diverso da esecuzione ad esecuzione) contiene righe del tipo:

[...]
Thread 2: Variabile --> 17
Thread 2: Variabile --> 18
Thread 2: Variabile --> 19
Thread 2: Variabile --> 20
Thread 2: Variabile --> 21
Thread 2: Variabile --> 22
Thread 1: Variabile --> 0
Thread 1: Variabile --> 0
Thread 1: Variabile --> 0
Thread 1: Variabile --> 0
Thread 1: Variabile --> 0
Thread 1: Variabile --> 0
Thread 2: Variabile --> 23
Thread 2: Variabile --> 1
Thread 2: Variabile --> 2
Thread 2: Variabile --> 3
Thread 2: Variabile --> 4

   •   Si osservi che il secondo thread, quando prende il controllo della console, ha una copia “sporca” del contatore nonostante il primo thread lo abbia azzerato più volte. Appena incrementata la variabile ‘i’, il contatore ritorna quello “azzerato”. Questo comportamento sembra essere tipico, anche eseguendo il programma più volte.
   •   Conclusione:
non condividere variabili tra più thread perché questo può produrre errori saltuari, difficili da rimediare. La soluzione corretta è quella di utilizzare meccanismi di sincronizzazione tra thread.
   •   Anche il ‘lock’ può risolvere il problema della sincronizzazione tra i due thread.

Lock tra thread e condivisione di variabili
   •   Per fare in modo che una parte di codice abbia accesso un solo thread per volta, si può utilizzare il lock
, assieme a una variabile di tipo riferimento.
   •   È sufficiente definire una variabile di tipo oggetto (nell’esempio ‘locker’) e proteggere il codice con il ‘lock’.
   •   Nell’esempio seguente i due thread sono eseguiti simultaneamente, ma la variabile ‘locker’ fa sì che il primo thread produca il proprio output e il secondo attende che il primo finisca e liberi il lock.
   •   Questo modo di bloccare i thread non è comunque efficiente, perché c’è sempre il fatto che un thread aspetta per poter procedere nella sua elaborazione e non garantisce il fatto che si possano introdurre errori di dead lock.

static object locker = new object();  

static void Main(string[] args)  
{  
   Thread tx = new Thread(WriteX);  
   Thread ty = new Thread(WriteY);  

   tx.Start();  
   ty.Start();  

   // Il thread principale non fa niente
   Thread.Sleep(5000);  
   Console.ReadKey();  
}  

static void WriteX()  
{  
   lock (locker)  
   {  
       for (int i = 0; i < 100; i++)  
       {  
           Console.WriteLine("x");  
       }  
   }  
}  

static void WriteY()  
{  
   lock (locker)  
   {  
       for (int i = 0; i < 100; i++)  
       {  
           Console.WriteLine(" y");  
       }  
   }  
}


   •   Ancora sull’esempio ‘azzeratore’ e ‘incrementatore’.
   •   Riprendiamo l’esempio dei due thread che agiscono sulla stessa variabile condivisa chiamata ‘contatore’; il primo thread è un ‘azzeratore’ e cerca di azzerare il contatore, il secondo thread è un ‘incrementatore’ e cerca di incrementare il contatore.
   •   Con un meccanismo di ‘lock’ si riesce a far sì che i due thread impostino il contatore in modo indipendente e sicuro.
   •   Esempio:


public static void MyThreadMethod1()  
{  
   for (int i = 0; i < 100; i++)  
   {  
       lock (locker)  
       {  
           contatore = 0;  
           Console.WriteLine("Thread 1: Variabile --> " + contatore);  
       }  
   }  
}  

public static void MyThreadMethod2()  
{  
   for (int i = 0; i < 100; i++)  
   {  
       lock (locker)  
       {  
           contatore++;  
           Console.WriteLine("Thread 2: Variabile --> " + contatore);  
       }  
   }  
}  


   •   L’output potrà essere diverso di volta in volta, in base alla schedulazione dei thread da parte del sistema operativo, ma i due thread agiranno in modo corretto: il primo avrà sempre il contatore azzerato, il secondo avrà sempre il contatore incrementato.
   •   Si veda: http://msdn.microsoft.com/en-us/library/orm-9780596527570-03-19.aspx

© 2020 Carlo Vecchio
Torna ai contenuti