다음을 통해 공유


방법: 낮은 수준의 동기화에 SpinLock 사용

다음 예제에서는 SpinLock을 사용하는 방법을 보여 줍니다.

예제

이 예제에서 임계 섹션은 최소량의 작업을 수행하므로 SpinLock의 후보가 되기에 적합합니다. 작업량을 조금 늘리면 표준 잠금에 비해 SpinLock의 성능이 향상됩니다. 그러나 일정 수준을 넘어서면 SpinLock의 효율성이 표준 잠금보다 더 떨어지기 시작합니다. Visual Studio Team Developer Edition 프로파일링 도구에 새로 도입된 동시성 프로파일링 기능을 사용하면 프로그램의 성능을 향상시키는 데 어떤 유형의 잠금이 더 적합한지 판단할 수 있습니다. 자세한 내용은 동시성 시각화 도우미를 참조하십시오.

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은 공유 리소스에 대한 잠금을 오래 유지할 필요가 없을 때 유용합니다. 이와 같은 경우 다중 코어 컴퓨터에서는 잠금이 해제될 때까지 차단된 스레드를 몇 차례 더 회전하는 것이 효율적일 수 있습니다. 스레드를 회전하면 스레드가 차단되지 않지만 이는 CPU 성능을 많이 필요로 하는 프로세스입니다. SpinLock은 특정 상황에서 회전을 중지하여 Hyper-Threading 기술을 통해 논리적 프로세서가 고갈되거나 시스템에 대한 우선 순위가 뒤바뀌는 경우를 방지합니다.

이 예제에는 다중 스레드 액세스를 위한 사용자 동기화를 필요로 하는 System.Collections.Generic.Queue<T> 클래스가 사용됩니다. .NET Framework 버전 4를 대상으로 하는 응용 프로그램에는 어떠한 사용자 잠금도 필요로 하지 않는 System.Collections.Concurrent.ConcurrentQueue<T>이라는 다른 옵션이 사용됩니다.

Exit 호출에 사용되는 false(Visual Basic의 경우 False)에 주목할 필요가 있습니다. 이렇게 하면 최상의 성능을 얻을 수 있습니다. IA64 아키텍처에서는 메모리 펜스를 사용하도록 true(True)를 지정합니다. 이렇게 하면 쓰기 버퍼를 플러시하여 다른 스레드를 끝내는 데 잠금을 사용할 수 있습니다.

참고 항목

기타 리소스

스레딩 개체 및 기능