Guide pratique pour utiliser le verrouillage SpinLock pour une synchronisation de bas niveau
L'exemple suivant montre comment utiliser un verrouillage SpinLock. Dans cet exemple, la section critique exécute une quantité minimale de travail, ce qui en fait un bon candidat pour un SpinLock. Le fait d’augmenter un petit peu le volume de travail augmente le niveau de performance du SpinLock par rapport à un verrouillage standard. Toutefois, à un moment, le verrouillage tournant devient plus coûteux qu’un verrouillage standard. Vous pouvez utiliser la fonctionnalité de profilage d'accès concurrentiel dans les outils de profilage pour voir quel type de verrouillage offre les meilleures performances pour votre programme. Pour plus d’informations, consultez Visualiseur concurrentiel.
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);
}
}
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
SpinLock peut être utile quand vous avez besoin d’un verrouillage de courte durée sur une ressource partagée. Dans ce cas, sur les ordinateurs multicœurs, le thread bloqué peut efficacement tourner pendant quelques cycles jusqu'à ce que le verrouillage soit libéré. En tournant, le thread ne se bloque pas. Ce processus est exigeant en ressources. SpinLock cesse de tourner sous certaines conditions pour empêcher toute défaillance des processeurs logiques ou toute inversion des priorités sur les systèmes avec Hyper-Threading.
Cet exemple utilise la classe System.Collections.Generic.Queue<T>, qui exige la synchronisation utilisateur pour l’accès multithread. Une autre option consiste à utiliser le System.Collections.Concurrent.ConcurrentQueue<T>, qui ne nécessite aucun verrouillage utilisateur.
Notez l’utilisation de false
dans l’appel à SpinLock.Exit. Cela fournit les meilleures performances. Dans les architectures IA64, spécifiez true
pour utiliser la barrière mémoire, qui vide les tampons d’écriture pour garantir que le verrouillage est disponible pour l’entrée des autres threads.