Best practice per la sincronizzazione dei thread .Net

La sincronizzazione è un concetto utilizzato per impedire a più thread di accedere contemporaneamente a una risorsa condivisa. Puoi usarlo per impedire a più thread di richiamare le proprietà oi metodi di un oggetto contemporaneamente. Tutto quello che devi fare è sincronizzare il blocco di codice che accede alla risorsa condivisa o sincronizzare le chiamate alle proprietà e ai membri dell'oggetto in modo che in un dato momento solo un thread possa entrare nella sezione critica.

Questo articolo presenta una discussione sui concetti relativi alla sincronizzazione e alla sicurezza dei thread in .Net e le procedure consigliate coinvolte.

Serratura esclusiva

Il blocco esclusivo viene utilizzato per garantire che in un dato momento, un solo thread possa entrare in una sezione critica. È necessario utilizzare uno dei seguenti per implementare blocchi esclusivi nell'applicazione.

  • Lock: questa è una scorciatoia sintattica per i metodi statici della classe Monitor e viene utilizzata per acquisire un blocco esclusivo su una risorsa condivisa
  • Mutex: simile alla parola chiave lock, tranne per il fatto che può funzionare su più processi
  • SpinLock: utilizzato per acquisire un blocco esclusivo su una risorsa condivisa evitando l'overhead del cambio di contesto del thread

È possibile utilizzare i metodi statici della classe Monitor o la parola chiave lock per implementare la thread safety nelle applicazioni. Sia i membri statici della classe Monitor che le parole chiave di blocco possono essere utilizzati per impedire l'accesso simultaneo a una risorsa condivisa. La parola chiave lock è solo una scorciatoia per implementare la sincronizzazione. Tuttavia, quando è necessario eseguire operazioni complesse in un'applicazione multithread, i metodi Wait () e Pulse () della classe Monitor possono essere utili.

Il frammento di codice seguente illustra come implementare la sincronizzazione utilizzando la classe Monitor.

private static readonly object lockObj = new object();

        static void Main(string[] args)

        {

            Monitor.Enter(lockObj);

                       try

            {

               //Some code

            }

                  finally

            {

                Monitor.Exit(lockObj);

            }

        }

Il codice equivalente che utilizza la parola chiave lock sarà simile a questo:

    private static readonly object lockObj = new object();

        static void Main(string[] args)

        {  

            try

            {

                lock(lockObj)

                {

                    //Some code

                }             

            }

            finally

            {

                //You can release any resources here

            }

        }

È possibile sfruttare la classe Mutex per implementare la sincronizzazione che può estendersi su più processi. Si noti che in modo simile all'istruzione lock, un blocco acquisito da un Mutex può essere rilasciato solo dallo stesso thread utilizzato per acquisire il blocco. L'acquisizione e il rilascio di blocchi utilizzando Mutex è relativamente più lento rispetto a fare lo stesso utilizzando l'istruzione lock.

L'idea principale alla base di SpinLock è di ridurre al minimo il costo coinvolto nel cambio di contesto tra i thread: se un thread può attendere o girare per un po 'di tempo prima di poter acquisire un blocco su una risorsa condivisa, è possibile evitare l'overhead coinvolto nel cambio di contesto tra i thread . Quando la sezione critica esegue una quantità minima di lavoro, può essere un buon candidato per uno SpinLock.

Serratura non esclusiva

È possibile sfruttare il blocco non esclusivo per limitare la concorrenza. Per implementare blocchi non esclusivi, è possibile utilizzare uno dei seguenti.

  • Semaforo: utilizzato per limitare il numero di thread che possono avere accesso a una risorsa condivisa contemporaneamente. In sostanza, viene utilizzato per limitare il numero di consumatori per una particolare risorsa condivisa contemporaneamente.
  • SemaphoreSlim: un'alternativa veloce e leggera alla classe Semaphore per implementare blocchi non esclusivi.
  • ReaderWriterLockSlim: la classe ReaderWriterLockSlim è stata introdotta in .Net Framework 3.5 in sostituzione della classe ReaderWriterLock.

È possibile utilizzare la classe ReaderWriterLockSlim per acquisire un blocco non esclusivo su una risorsa condivisa che richiederebbe letture frequenti ma aggiornamenti poco frequenti. Quindi, invece di un blocco mutuamente esclusivo su una risorsa condivisa che necessita di letture frequenti e aggiornamenti poco frequenti, è possibile utilizzare questa classe per acquisire un blocco di lettura sulla risorsa condivisa e un blocco di scrittura esclusivo su di essa.

Deadlock

È necessario evitare di utilizzare un'istruzione lock sul tipo o utilizzare istruzioni come lock (this) per implementare la sincronizzazione nell'applicazione in quanto ciò potrebbe causare deadlock. Si noti che possono verificarsi deadlock anche se si mantiene il blocco acquisito su una risorsa condivisa per un periodo di tempo più lungo. Non dovresti usare tipi immutabili nelle tue istruzioni di blocco. Ad esempio, dovresti evitare di utilizzare un oggetto stringa come chiave nell'istruzione lock. È consigliabile evitare di utilizzare l'istruzione lock su un tipo pubblico: è buona norma bloccare gli oggetti privati ​​o protetti che non sono interni. In sostanza, una situazione di deadlock si verifica quando più thread sono in attesa che rilascino il blocco su una risorsa condivisa. È possibile fare riferimento a questo articolo di MSDN per saperne di più sui deadlock.