方法: 下位レベルの同期に SpinLock を使用するHow to: use SpinLock for low-level synchronization

SpinLock の使用例を以下に示します。The following example demonstrates how to use a SpinLock. この例では、クリティカル セクションが実行する作業量は最小限であるため、SpinLock に適しています。In this example, the critical section performs a minimal amount of work, which makes it a good candidate for a SpinLock. 作業量を少し増やすと、標準ロックと比較して SpinLock のパフォーマンスは高まります。Increasing the work a small amount increases the performance of the SpinLock compared to a standard lock. ただし、SpinLock が標準ロックよりも高負荷になるポイントがあります。However, there is a point at which a SpinLock becomes more expensive than a standard lock. プロファイリング ツールでコンカレンシープロファイリング機能を使用して、どのタイプのロックを使用すればプログラムのパフォーマンスが高まるかを確認できます。You can use the concurrency profiling functionality in the profiling tools to see which type of lock provides better performance in your program. 詳細については、「コンカレンシー ビジュアライザー」を参照してください。For more information, see Concurrency Visualizer.


 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 は、共有リソースのロックが非常に長い期間使用されない場合に有用なことがあります。SpinLock might be useful when a lock on a shared resource is not going to be held for very long. そのような場合、マルチコア コンピューターでは、ロックが解除されるまで数回のサイクルの間、ブロックされたスレッドをスピンさせると効率が高まることがあります。In such cases, on multi-core computers it can be efficient for the blocked thread to spin for a few cycles until the lock is released. スピンするとスレッドはブロックされなくなりますが、これは CPU 負荷の高いプロセスです。By spinning, the thread does not become blocked, which is a CPU-intensive process. ハイパースレッディングを使用するシステムでは、SpinLock は特定の状況でスピンを停止して、論理プロセッサの不足や優先順位の逆転が発生するのを回避します。SpinLock will stop spinning under certain conditions to prevent starvation of logical processors or priority inversion on systems with Hyper-Threading.

この例では、System.Collections.Generic.Queue<T> クラスを使用するため、マルチスレッド アクセスにはユーザーによる同期が必要になります。This example uses the System.Collections.Generic.Queue<T> class, which requires user synchronization for multi-threaded access. .NET Framework Version 4 をターゲットにしているアプリケーションでは、ユーザーのロックが不要な System.Collections.Concurrent.ConcurrentQueue<T> を使用することもできます。In applications that target the .NET Framework version 4, another option is to use the System.Collections.Concurrent.ConcurrentQueue<T>, which does not require any user locks.

SpinLock.Exit の呼び出しに false (Visual Basic では False) が使用されていることに注目してください。Note the use of false (False in Visual Basic) in the call to SpinLock.Exit. これにより、最適なパフォーマンスを得られます。This provides the best performance. メモリ フェンスを使用するには、IA64 アーキテクチャで true (Visual Basic では True) を指定します。これにより、書き込みバッファーがフラッシュされるので、ロックを使用して他のスレッドを終了できるようになります。Specify true (True in Visual Basic) on IA64 architectures to use the memory fence, which flushes the write buffers to ensure that the lock is now available for other threads to exit.

関連項目See also