方法: 下位レベルの同期に 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) を指定します。これにより、書き込みバッファーがフラッシュされるので、ロックを使用して他のスレッドを終了できるようになります。