SpinLock 構造体

定義

ロックが使用可能になるまで繰り返しチェックするループ内で、ロックを取得しようとするスレッドが待機する相互排他ロック プリミティブを提供します。

public value class SpinLock
public struct SpinLock
[System.Runtime.InteropServices.ComVisible(false)]
public struct SpinLock
type SpinLock = struct
[<System.Runtime.InteropServices.ComVisible(false)>]
type SpinLock = struct
Public Structure SpinLock
継承
SpinLock
属性

次の例は、次のコードを使用する方法を SpinLock示しています。

using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

class SpinLockDemo
{

    // Demonstrates:
    //      Default SpinLock construction ()
    //      SpinLock.Enter(ref bool)
    //      SpinLock.Exit()
    static void SpinLockSample1()
    {
        SpinLock sl = new SpinLock();

        StringBuilder sb = new StringBuilder();

        // Action taken by each parallel job.
        // Append to the StringBuilder 10000 times, protecting
        // access to sb with a SpinLock.
        Action action = () =>
        {
            bool gotLock = false;
            for (int i = 0; i < 10000; i++)
            {
                gotLock = false;
                try
                {
                    sl.Enter(ref gotLock);
                    sb.Append((i % 10).ToString());
                }
                finally
                {
                    // Only give up the lock if you actually acquired it
                    if (gotLock) sl.Exit();
                }
            }
        };

        // Invoke 3 concurrent instances of the action above
        Parallel.Invoke(action, action, action);

        // Check/Show the results
        Console.WriteLine("sb.Length = {0} (should be 30000)", sb.Length);
        Console.WriteLine("number of occurrences of '5' in sb: {0} (should be 3000)",
            sb.ToString().Where(c => (c == '5')).Count());
    }

    // Demonstrates:
    //      Default SpinLock constructor (tracking thread owner)
    //      SpinLock.Enter(ref bool)
    //      SpinLock.Exit() throwing exception
    //      SpinLock.IsHeld
    //      SpinLock.IsHeldByCurrentThread
    //      SpinLock.IsThreadOwnerTrackingEnabled
    static void SpinLockSample2()
    {
        // Instantiate a SpinLock
        SpinLock sl = new SpinLock();

        // These MRESs help to sequence the two jobs below
        ManualResetEventSlim mre1 = new ManualResetEventSlim(false);
        ManualResetEventSlim mre2 = new ManualResetEventSlim(false);
        bool lockTaken = false;

        Task taskA = Task.Factory.StartNew(() =>
        {
            try
            {
                sl.Enter(ref lockTaken);
                Console.WriteLine("Task A: entered SpinLock");
                mre1.Set(); // Signal Task B to commence with its logic

                // Wait for Task B to complete its logic
                // (Normally, you would not want to perform such a potentially
                // heavyweight operation while holding a SpinLock, but we do it
                // here to more effectively show off SpinLock properties in
                // taskB.)
                mre2.Wait();
            }
            finally
            {
                if (lockTaken) sl.Exit();
            }
        });

        Task taskB = Task.Factory.StartNew(() =>
        {
            mre1.Wait(); // wait for Task A to signal me
            Console.WriteLine("Task B: sl.IsHeld = {0} (should be true)", sl.IsHeld);
            Console.WriteLine("Task B: sl.IsHeldByCurrentThread = {0} (should be false)", sl.IsHeldByCurrentThread);
            Console.WriteLine("Task B: sl.IsThreadOwnerTrackingEnabled = {0} (should be true)", sl.IsThreadOwnerTrackingEnabled);

            try
            {
                sl.Exit();
                Console.WriteLine("Task B: Released sl, should not have been able to!");
            }
            catch (Exception e)
            {
                Console.WriteLine("Task B: sl.Exit resulted in exception, as expected: {0}", e.Message);
            }

            mre2.Set(); // Signal Task A to exit the SpinLock
        });

        // Wait for task completion and clean up
        Task.WaitAll(taskA, taskB);
        mre1.Dispose();
        mre2.Dispose();
    }

    // Demonstrates:
    //      SpinLock constructor(false) -- thread ownership not tracked
    static void SpinLockSample3()
    {
        // Create SpinLock that does not track ownership/threadIDs
        SpinLock sl = new SpinLock(false);

        // Used to synchronize with the Task below
        ManualResetEventSlim mres = new ManualResetEventSlim(false);

        // We will verify that the Task below runs on a separate thread
        Console.WriteLine("main thread id = {0}", Thread.CurrentThread.ManagedThreadId);

        // Now enter the SpinLock.  Ordinarily, you would not want to spend so
        // much time holding a SpinLock, but we do it here for the purpose of 
        // demonstrating that a non-ownership-tracking SpinLock can be exited 
        // by a different thread than that which was used to enter it.
        bool lockTaken = false;
        sl.Enter(ref lockTaken);

        // Create a separate Task from which to Exit() the SpinLock
        Task worker = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("worker task thread id = {0} (should be different than main thread id)",
                Thread.CurrentThread.ManagedThreadId);

            // Now exit the SpinLock
            try
            {
                sl.Exit();
                Console.WriteLine("worker task: successfully exited SpinLock, as expected");
            }
            catch (Exception e)
            {
                Console.WriteLine("worker task: unexpected failure in exiting SpinLock: {0}", e.Message);
            }

            // Notify main thread to continue
            mres.Set();
        });

        // Do this instead of worker.Wait(), because worker.Wait() could inline the worker Task,
        // causing it to be run on the same thread.  The purpose of this example is to show that
        // a different thread can exit the SpinLock created (without thread tracking) on your thread.
        mres.Wait();

        // now Wait() on worker and clean up
        worker.Wait();
        mres.Dispose();
    }
}
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks


Module SpinLockDemo

    ' Demonstrates:
    ' Default SpinLock construction ()
    ' SpinLock.Enter(ref bool)
    ' SpinLock.Exit()
    Private Sub SpinLockSample1()
        Dim sl As New SpinLock()

        Dim sb As New StringBuilder()

        ' Action taken by each parallel job.
        ' Append to the StringBuilder 10000 times, protecting
        ' access to sb with a SpinLock.
        Dim action As Action =
            Sub()
                Dim gotLock As Boolean = False
                For i As Integer = 0 To 9999
                    gotLock = False
                    Try
                        sl.Enter(gotLock)
                        sb.Append((i Mod 10).ToString())
                    Finally
                        ' Only give up the lock if you actually acquired it
                        If gotLock Then
                            sl.[Exit]()
                        End If
                    End Try
                Next
            End Sub

        ' Invoke 3 concurrent instances of the action above
        Parallel.Invoke(action, action, action)

        ' Check/Show the results
        Console.WriteLine("sb.Length = {0} (should be 30000)", sb.Length)
        Console.WriteLine("number of occurrences of '5' in sb: {0} (should be 3000)", sb.ToString().Where(Function(c) (c = "5"c)).Count())
    End Sub

    ' Demonstrates:
    ' Default SpinLock constructor (tracking thread owner)
    ' SpinLock.Enter(ref bool)
    ' SpinLock.Exit() throwing exception
    ' SpinLock.IsHeld
    ' SpinLock.IsHeldByCurrentThread
    ' SpinLock.IsThreadOwnerTrackingEnabled
    Private Sub SpinLockSample2()
        ' Instantiate a SpinLock
        Dim sl As New SpinLock()

        ' These MRESs help to sequence the two jobs below
        Dim mre1 As New ManualResetEventSlim(False)
        Dim mre2 As New ManualResetEventSlim(False)
        Dim lockTaken As Boolean = False

        Dim taskA As Task = Task.Factory.StartNew(
            Sub()
                Try
                    sl.Enter(lockTaken)
                    Console.WriteLine("Task A: entered SpinLock")
                    mre1.[Set]()
                    ' Signal Task B to commence with its logic
                    ' Wait for Task B to complete its logic
                    ' (Normally, you would not want to perform such a potentially
                    ' heavyweight operation while holding a SpinLock, but we do it
                    ' here to more effectively show off SpinLock properties in
                    ' taskB.)
                    mre2.Wait()
                Finally
                    If lockTaken Then
                        sl.[Exit]()
                    End If
                End Try
            End Sub)

        Dim taskB As Task = Task.Factory.StartNew(
            Sub()
                mre1.Wait()
                ' wait for Task A to signal me
                Console.WriteLine("Task B: sl.IsHeld = {0} (should be true)", sl.IsHeld)
                Console.WriteLine("Task B: sl.IsHeldByCurrentThread = {0} (should be false)", sl.IsHeldByCurrentThread)
                Console.WriteLine("Task B: sl.IsThreadOwnerTrackingEnabled = {0} (should be true)", sl.IsThreadOwnerTrackingEnabled)

                Try
                    sl.[Exit]()
                    Console.WriteLine("Task B: Released sl, should not have been able to!")
                Catch e As Exception
                    Console.WriteLine("Task B: sl.Exit resulted in exception, as expected: {0}", e.Message)
                End Try

                ' Signal Task A to exit the SpinLock
                mre2.[Set]()
            End Sub)

        ' Wait for task completion and clean up
        Task.WaitAll(taskA, taskB)
        mre1.Dispose()
        mre2.Dispose()
    End Sub

    ' Demonstrates:
    ' SpinLock constructor(false) -- thread ownership not tracked
    Private Sub SpinLockSample3()
        ' Create SpinLock that does not track ownership/threadIDs
        Dim sl As New SpinLock(False)

        ' Used to synchronize with the Task below
        Dim mres As New ManualResetEventSlim(False)

        ' We will verify that the Task below runs on a separate thread
        Console.WriteLine("main thread id = {0}", Thread.CurrentThread.ManagedThreadId)

        ' Now enter the SpinLock.  Ordinarily, you would not want to spend so
        ' much time holding a SpinLock, but we do it here for the purpose of 
        ' demonstrating that a non-ownership-tracking SpinLock can be exited 
        ' by a different thread than that which was used to enter it.
        Dim lockTaken As Boolean = False
        sl.Enter(lockTaken)

        ' Create a separate Task
        Dim worker As Task = Task.Factory.StartNew(
            Sub()
                Console.WriteLine("worker task thread id = {0} (should be different than main thread id)", Thread.CurrentThread.ManagedThreadId)

                ' Now exit the SpinLock
                Try
                    sl.[Exit]()
                    Console.WriteLine("worker task: successfully exited SpinLock, as expected")
                Catch e As Exception
                    Console.WriteLine("worker task: unexpected failure in exiting SpinLock: {0}", e.Message)
                End Try

                ' Notify main thread to continue
                mres.[Set]()
            End Sub)

        ' Do this instead of worker.Wait(), because worker.Wait() could inline the worker Task,
        ' causing it to be run on the same thread. The purpose of this example is to show that
        ' a different thread can exit the SpinLock created (without thread tracking) on your thread.
        mres.Wait()

        ' now Wait() on worker and clean up
        worker.Wait()
        mres.Dispose()
    End Sub


End Module

注釈

スピン ロックを使用する方法の例については、「 方法: Low-Level同期に SpinLock を使用する」を参照してください。

スピン ロックはリーフ レベルのロックに使用できます。この場合、オブジェクトの割り当ては、サイズが大 Monitorきいか、ガベージ コレクションの負荷が高すぎるために暗示されます。 スピン ロックはブロックを回避するのに役立ちます。ただし、大量のブロックが予想される場合は、スピンが過剰であるためにスピン ロックを使用しないことをお考えください。 スピンは、ロックが細かく、数が多い場合 (リンク リストのノードごとのロックなど) や、ロックの保持時間が常に非常に短い場合に役立ちます。 一般に、スピン ロックを保持している間は、次のいずれかのアクションを回避する必要があります。

  • ブロック

  • それ自体がブロックする可能性のあるものを呼び出す

  • 一度に複数のスピンロックを保持し、

  • 動的にディスパッチされた呼び出し (インターフェイスと仮想) を行う

  • 所有していないコードに静的にディスパッチされた呼び出しを行う、または

  • メモリの割り当て。

SpinLock は、アプリケーションのパフォーマンスを向上させると判断した後にのみ使用する必要があります。 また、パフォーマンス上の理由から、値型であることに SpinLock 注意することも重要です。 このため、2 つのインスタンス (元のインスタンスとコピー) は互いに完全に独立しているため、誤ってインスタンスをコピー SpinLock しないように注意する必要があります。これにより、アプリケーションの誤った動作が発生する可能性があります。 インスタンスを SpinLock 渡す必要がある場合は、値ではなく参照によって渡す必要があります。

読み取り専用フィールドにインスタンスを格納 SpinLock しないでください。

コンストラクター

SpinLock(Boolean)

デバッグを向上させるためにスレッド ID を追跡するオプションを使用して、SpinLock 構造体の新しいインスタンスを初期化します。

プロパティ

IsHeld

ロックが現在いずれかのスレッドによって保持されているかどうかを取得します。

IsHeldByCurrentThread

ロックが現在のスレッドによって保持されているかどうかを取得します。

IsThreadOwnerTrackingEnabled

このインスタンスに対してスレッド所有権の追跡が有効になっているかどうかを取得します。

メソッド

Enter(Boolean)

メソッド呼び出し内で例外が発生した場合でも、lockTaken を確実に確認して、ロックが取得されたかどうかを判断できるような信頼性の高い方法で、ロックを取得します。

Exit()

ロックを解放します。

Exit(Boolean)

ロックを解放します。

TryEnter(Boolean)

メソッド呼び出し内で例外が発生した場合でも、lockTaken を確実に確認して、ロックが取得されたかどうかを判断できるような信頼性の高い方法で、ロックの取得を試みます。

TryEnter(Int32, Boolean)

メソッド呼び出し内で例外が発生した場合でも、lockTaken を確実に確認して、ロックが取得されたかどうかを判断できるような信頼性の高い方法で、ロックの取得を試みます。

TryEnter(TimeSpan, Boolean)

メソッド呼び出し内で例外が発生した場合でも、lockTaken を確実に確認して、ロックが取得されたかどうかを判断できるような信頼性の高い方法で、ロックの取得を試みます。

適用対象

スレッド セーフ

すべてのメンバーはスレッド セーフであり、複数の SpinLock スレッドから同時に使用できます。

こちらもご覧ください