Comment : utiliser le verrouillage spinlock pour une synchronisation de bas niveau

L'exemple suivant montre comment utiliser un SpinLock.

Exemple

Dans cet exemple, la section critique exécute une quantité minimale de travail, ce qui en fait un bon candidat pour un SpinLock. La légère augmentation de la quantité de travail améliore les performances du SpinLock, comparé à un verrou standard. Toutefois, au-delà d'un certain point, un verrouillage tournant devient plus coûteux qu'un verrou standard. Vous pouvez utiliser la nouvelle fonctionnalité de profilage d'accès concurrentiel dans les outils de profilage Visual Studio Team Developer pour savoir quel type de verrou fournit les meilleures performances pour votre programme. Pour plus d'informations, consultez Visualiseur concurrence.

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 peut être utile lorsqu'un verrou placé sur une ressource partagée ne doit pas être maintenu très longtemps. Dans ce cas, sur les ordinateurs multicœurs, il peut être efficace pour le thread bloqué de tourner pendant quelques cycles jusqu'à ce que le verrou soit libéré. En tournant, le thread ne se bloque pas, ce qui est un processus gourmand en ressources processeur. SpinLock cessera de tourner sous certaines conditions pour empêcher la privation des processeurs logiques ou l'inversion de priorité sur les systèmes équipés de la technologie Hyper-Threading.

Cet exemple utilise la classe System.Collections.Generic.Queue<T>, qui requiert la synchronisation utilisateur pour l'accès multithread. Dans les applications qui ciblent .NET Framework 4, une autre option consiste à utiliser le System.Collections.Concurrent.ConcurrentQueue<T>, qui ne requiert aucun verrouillage utilisateur.

Notez l'utilisation de false (False en Visual Basic) dans l'appel à Exit. Cela fournit de meilleures performances. Spécifiez true (True) sur les architectures IA64 pour utiliser la barrière mémoire, qui vide les mémoires tampons d'écriture pour vérifier que les autres threads peuvent désormais quitter le verrou.

Voir aussi

Autres ressources

Fonctionnalités et objets de threading