Procedura: utilizzare SpinLock per la sincronizzazione di basso livello
Nell'esempio seguente viene illustrato come utilizzare un oggetto SpinLock.
Esempio
In questo esempio la sezione di importanza principale esegue una quantità minima di lavoro e pertanto risulta essere un ottimo candidato per SpinLock. Se si aumenta il lavoro di una piccola quantità si ottiene un aumento delle prestazioni di SpinLock rispetto a un blocco standard. Tuttavia, esiste un punto in cui SpinLock diventa più oneroso di un blocco standard. È possibile utilizzare la nuova funzionalità di profilo della concorrenza in Strumenti di analisi di Visual Studio Team Developer Edition per vedere quale tipo di blocco fornisce le prestazioni migliori nel programma. Per ulteriori informazioni, vedere Visualizzatore di concorrenze.
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Class SpinLockDemo2
Const N As Integer = 100000
Shared _queue = New Queue(Of Data)()
Shared _lock = New Object()
Shared _spinlock = New SpinLock()
Class Data
Public Name As String
Public Number As Double
End Class
Shared Sub Main()
' First use a standard lock for comparison purposes.
UseLock()
_queue.Clear()
UseSpinLock()
Console.WriteLine("Press a key")
Console.ReadKey()
End Sub
Private Shared Sub UpdateWithSpinLock(ByVal d As Data, ByVal i As Integer)
Dim lockTaken As Boolean = False
Try
_spinlock.Enter(lockTaken)
_queue.Enqueue(d)
Finally
If lockTaken Then
_spinlock.Exit(False)
End If
End Try
End Sub
Private Shared Sub UseSpinLock()
Dim sw = Stopwatch.StartNew()
Parallel.Invoke(
Sub()
For i As Integer = 0 To N - 1
UpdateWithSpinLock(New Data() With {.Name = i.ToString(), .Number = i}, i)
Next
End Sub,
Sub()
For i As Integer = 0 To N - 1
UpdateWithSpinLock(New Data() With {.Name = i.ToString(), .Number = i}, i)
Next
End Sub
)
sw.Stop()
Console.WriteLine("elapsed ms with spinlock: {0}", sw.ElapsedMilliseconds)
End Sub
Shared Sub UpdateWithLock(ByVal d As Data, ByVal i As Integer)
SyncLock (_lock)
_queue.Enqueue(d)
End SyncLock
End Sub
Private Shared Sub UseLock()
Dim sw = Stopwatch.StartNew()
Parallel.Invoke(
Sub()
For i As Integer = 0 To N - 1
UpdateWithLock(New Data() With {.Name = i.ToString(), .Number = i}, i)
Next
End Sub,
Sub()
For i As Integer = 0 To N - 1
UpdateWithLock(New Data() With {.Name = i.ToString(), .Number = i}, i)
Next
End Sub
)
sw.Stop()
Console.WriteLine("elapsed ms with lock: {0}", sw.ElapsedMilliseconds)
End Sub
End Class
class SpinLockDemo2
{
const int N = 100000;
static Queue<Data> _queue = new Queue<Data>();
static object _lock = new Object();
static SpinLock _spinlock = new SpinLock();
class Data
{
public string Name { get; set; }
public double Number { get; set; }
}
static void Main(string[] args)
{
// First use a standard lock for comparison purposes.
UseLock();
_queue.Clear();
UseSpinLock();
Console.WriteLine("Press a key");
Console.ReadKey();
}
private static void UpdateWithSpinLock(Data d, int i)
{
bool lockTaken = false;
try
{
_spinlock.Enter(ref lockTaken);
_queue.Enqueue( d );
}
finally
{
if (lockTaken) _spinlock.Exit(false);
}
}
private static void UseSpinLock()
{
Stopwatch sw = Stopwatch.StartNew();
Parallel.Invoke(
() => {
for (int i = 0; i < N; i++)
{
UpdateWithSpinLock(new Data() { Name = i.ToString(), Number = i }, i);
}
},
() => {
for (int i = 0; i < N; i++)
{
UpdateWithSpinLock(new Data() { Name = i.ToString(), Number = i }, i);
}
}
);
sw.Stop();
Console.WriteLine("elapsed ms with spinlock: {0}", sw.ElapsedMilliseconds);
}
static void UpdateWithLock(Data d, int i)
{
lock (_lock)
{
_queue.Enqueue(d);
}
}
private static void UseLock()
{
Stopwatch sw = Stopwatch.StartNew();
Parallel.Invoke(
() => {
for (int i = 0; i < N; i++)
{
UpdateWithLock(new Data() { Name = i.ToString(), Number = i }, i);
}
},
() => {
for (int i = 0; i < N; i++)
{
UpdateWithLock(new Data() { Name = i.ToString(), Number = i }, i);
}
}
);
sw.Stop();
Console.WriteLine("elapsed ms with lock: {0}", sw.ElapsedMilliseconds);
}
}
SpinLock potrebbe essere utile quando un blocco su una risorsa condivisa non verrà mantenuto per molto tempo. In questi casi, nei computer multicore uno spin di alcuni cicli del thread bloccato fino al rilascio del blocco può risultare efficiente. Lo spin impedisce il blocco del thread, un processo che richiede un elevato consumo della CPU. Per impedire l'esaurimento di risorse dei processori logici o l'inversione di priorità nei sistemi con Hyper-Threading, SpinLock interromperà lo spin in determinate condizioni.
In questo esempio viene utilizzata la classe System.Collections.Generic.Queue<T> che richiede la sincronizzazione dell'utente per l'accesso multithreading. Nelle applicazioni destinate a .NET Framework versione 4, un'altra possibilità è utilizzare System.Collections.Concurrent.ConcurrentQueue<T>, poiché non richiede blocchi utente.
Si noti l'utilizzo di false (False in Visual Basic) nella chiamata a Exit. Ciò fornisce le migliori prestazioni. Specificare true (True) nelle architetture IA64 per utilizzare il limite di memoria che scarica il buffer di scrittura per assicurarsi che il blocco sia ora disponibile per l'uscita di altri thread.