Aracılığıyla paylaş


Nasıl yapılır: Düşük düzeyli eşitleme için SpinLock kullanma

Aşağıdaki örnek, bir SpinLock nasıl kullanılacağını göstermektedir. Bu örnekte, kritik bölüm minimum düzeyde iş yapar ve bu da onu iyi bir SpinLock aday haline getirir. İşin biraz artırılması, SpinLock'ın performansını standart bir kilitle karşılaştırıldığında artırır. Ancak, SpinLock'un standart kilitten daha pahalı olduğu bir nokta vardır. Hangi kilit türünün programınızda daha iyi performans sağladığını görmek için profil oluşturma araçlarında eşzamanlılık profili oluşturma işlevini kullanabilirsiniz. Daha fazla bilgi için bkz . Eşzamanlılık Görselleştiricisi.


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: {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: {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 paylaşılan bir kaynak üzerindeki kilit çok uzun süre tutulmayacaksa yararlı olabilir. Böyle durumlarda, çok çekirdekli bilgisayarlarda kilit serbest bırakılana kadar engellenen iş parçacığının birkaç döngü boyunca dönmesi verimli olabilir. İşlem döndüğü için, iş parçacığı engellenmez; bu da yoğun CPU kullanımına neden olur. SpinLock , Hyper-Threading'e sahip sistemlerde mantıksal işlemcilerin kaynak yetersizliğini veya öncelik tersine dönüşünü önlemek için belirli koşullar altında döndürmeyi durdurur.

Bu örnek, çok iş parçacıklı erişim için kullanıcı eşitlemesi gerektiren System.Collections.Generic.Queue<T> sınıfını kullanır. Başka bir seçenek, herhangi bir kullanıcı kilidi gerektirmeyen System.Collections.Concurrent.ConcurrentQueue<T>'yi kullanmaktır.

SpinLock.Exit çağrısında false kullanımına dikkat edin. Bu, en iyi performansı sağlar. IA64 mimarilerinde bellek çitini kullanmak için true belirtin. Bu işlem, yazma arabelleklerini temizleyerek kilidin diğer iş parçacıklarının erişimine açılmasını sağlar.

Ayrıca bkz.