Capire come [[sincronizzazione dei thread in C#]] è indispensabile per poter costruire applicazioni veloci e thread-safe; e in questo contesto è necessario avere chiara la differenza tra blocking e spinning.
Spesso può essere utile mettere in pausa un Thread in modo da aspettare che una determinata condizione avvenga, per ottenere questo ci sono due modi: il blocking e lo spinning.
Tl;Dr: il blocking rilascia il processore in modo che esso possa dedicarsi ad altri thread, mentre lo spinning lancia un loop che tiene il processore occupato sul thread corrente.
Yielding e context switch
Un Thread bloccato restituisce subito la sua gestione al processore e non consuma più risorse fino a che la condizione non è stata rispettata.
Per fare queste operazioni quando un Thread si blocca o si sblocca il sistema operativo opera quello definito come context switch che porta un overhead di qualche microsecondo.
Lo yielding di un thread è l’operazione per cui un thread informa lo scheduler che esso rilascia la slot di tempo assegnata, sarà il sistema operativo a scegliere a quale thread passare. Se non ho alcun thread rimarrò nel thread corrente.
In C# lo yielding può essere chiamato esplicitamente con l’istruzione Thread.Yield()
.
Blocking
Il modo più comune è il blocking che consiste nel fermare un thread nell’attesa di un segnale (signaling) o che si liberi una risorsa (locking).
Esempi di classi per il signaling sono i ManualResetEvent o AutoResetEvent
, poi ci sono anche classi secondarie come il Countdownevent
o Barrier
, entrambi introdotti in .NET 4.
Il costrutto principale per il locking invece è appunto il lock
che permette di ottenere accesso esclusivo ad un metodo bloccando tutti gli altri fino che questo ultimo non viene rilasciato.
Alternative al lock sono la classe Mutex
e la classe Semaphore
.
Spinning
Lo spinning è invece la modalità di blocco di un thread per cui questo rimane bloccato in una attività CPU intensive senza alcun context switch.
L’esempio classico nel blocco con spinning è un ciclo while
senza corpo, per esempio:
Questo tipo di blocco è estremamente CPU-intensive in quanto la CLR (e conseguentemente il sistema operativo) continuano a effettuare calcoli e controlli alla massima velocità possibile.
Spesso infatti si utilizza un ibrido tra blocking e spinning utilizzando qualcosa come:
che è sicuramente più efficiente dello spinning puro, anche se esteticamente non è il massimo…
Chi preferire?
Lo spinning può essere migliore del blocking quando mi aspetto che una condizione sia verificata subito (nell’ordine di microsecondi) in quando evito l’overhead e la latenza del context switch necessario ai metodi blocking.
In tutti gli altri il blocking è da preferire.