次の方法で共有


方法: 下位レベルの同期に 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 は特定の状況でスピンを停止して、論理プロセッサ リソースの不足や優先順位の逆転が発生するのを防ぎます。

この例では System.Collections.Generic.Queue<T> クラスを使用しているため、マルチスレッド アクセスにはユーザーによる同期が必要となります。 .NET Framework Version 4 を対象とするアプリケーションでは、ユーザー ロックを必要としない System.Collections.Concurrent.ConcurrentQueue<T> を使用する方法もあります。

Exit の呼び出しでは false (Visual Basic では False) を使用していることに注意してください。 これにより、最適なパフォーマンスが得られます。 メモリ フェンスを使用するには、IA64 アーキテクチャで true (True) を指定します。これにより、書き込みバッファーがフラッシュされるので、ロックを使用して他のスレッドを終了できるようになります。

参照

その他の技術情報

スレッド処理オブジェクトと機能