Delen via


Procedure: SpinLock gebruiken voor synchronisatie op laag niveau

In het volgende voorbeeld ziet u hoe u een SpinLock. In dit voorbeeld voert de kritieke sectie een minimale hoeveelheid werk uit, waardoor het een goede kandidaat voor een SpinLockis. Het verhogen van het werk verhoogt de prestaties van de SpinLock vergelijking met een standaardvergrendeling. Er is echter een punt waarop een SpinLock duurder wordt dan een standaardvergrendeling. U kunt de functionaliteit voor gelijktijdigheidsprofilering in de profileringsprogramma's gebruiken om te zien welk type vergrendeling betere prestaties biedt in uw programma. Zie Gelijktijdigheid visualiseren voor meer informatie.


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 kan handig zijn wanneer een vergrendeling op een gedeelde resource niet erg lang wordt bewaard. In dergelijke gevallen kan het op computers met meerdere kernen efficiƫnt zijn voor de geblokkeerde thread om een paar cycli te draaien totdat de vergrendeling wordt vrijgegeven. Door te draaien, wordt de thread niet geblokkeerd, wat een CPU-intensief proces is. SpinLock stopt met draaien onder bepaalde voorwaarden om verhongering van logische processors of prioriteitsinversion op systemen met Hyper-Threading te voorkomen.

In dit voorbeeld wordt de System.Collections.Generic.Queue<T> klasse gebruikt. Hiervoor is gebruikerssynchronisatie vereist voor toegang met meerdere threads. Een andere optie is het gebruik van de System.Collections.Concurrent.ConcurrentQueue<T>, waarvoor geen gebruikersvergrendelingen zijn vereist.

Let op het gebruik van false in de aanroep naar SpinLock.Exit. Dit biedt de beste prestaties. Geef true op IA64-architecturen op om het geheugenheining te gebruiken, waardoor de schrijfbuffers worden leeggemaakt om ervoor te zorgen dat de vergrendeling nu beschikbaar is voor andere threads die moeten worden ingevoerd.

Zie ook