Cómo: Utilizar SpinLock para la sincronización de bajo nivel

En el siguiente ejemplo se muestra cómo utilizar SpinLock.

Ejemplo

En este ejemplo, la sección crítica realiza una cantidad de trabajo mínima, lo que lo convierte en un buen candidato para SpinLock. Al aumentar ligeramente el trabajo aumenta el rendimiento de SpinLock en comparación con un bloqueo estándar. Sin embargo, hay un punto en el que un bloqueo por subproceso es más caro que un bloqueo estándar. Puede usar la nueva funcionalidad de generación de perfiles de simultaneidad en Herramientas de generación de perfiles de Visual Studio Team Developer para ver qué tipo de bloqueo proporciona mayor rendimiento en su programa. Para obtener más información, vea Visualizador de simultaneidad.

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 puede resultar útil cuando un bloqueo en un recurso compartido no se va a mantener durante mucho tiempo. En esos casos, en equipos con varios núcleos puede ser eficaz que el subproceso bloqueado gire durante unos ciclos hasta que se libere el bloqueo. Al girar, el subproceso no se bloquea, lo cual es un proceso que consume muchos recursos de la CPU. SpinLock dejará de girar en ciertas condiciones para evitar el colapso de los procesadores lógicos o la inversión de la prioridad en sistemas con Hyper-Threading.

En este ejemplo se usa la clase System.Collections.Generic.Queue<T>, que necesita sincronización de usuarios para el acceso multiproceso. En aplicaciones destinadas a .NET Framework 4, otra opción consiste en usar System.Collections.Concurrent.ConcurrentQueue<T>, que no necesita ningún bloqueo de usuario.

Fíjese en el uso de false (False en Visual Basic) en la llamada a Exit. Este valor proporciona el mayor rendimiento. Especifique true (True) en las arquitecturas IA64 para utilizar la barrera de memoria; de este modo, se vacían los búferes de escritura a fin de garantizar que el bloqueo está disponible para otros subprocesos que van a salir.

Vea también

Otros recursos

Objetos y características de subprocesos