Postupy: Použití SpinLocku pro synchronizaci nízké úrovně
Následující příklad ukazuje, jak použít SpinLock. V tomto příkladu provádí kritická část minimální množství práce, což z něj dělá vhodného kandidáta na .SpinLock Zvýšením práce o malé množství se zvýší výkon SpinLock ve srovnání se standardním zámkem. Existuje však bod, kdy se SpinLock stává dražší než standardní zámek. Pomocí funkce profilace souběžnosti v nástrojích pro profilaci můžete zjistit, jaký typ zámku poskytuje ve vašem programu lepší výkon. Další informace najdete v tématu Vizualizér souběžnosti.
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 může být užitečné, když se zámek na sdíleném prostředku nebude uchovávat příliš dlouho. V takových případech může být na počítačích s více jádry efektivní, aby blokované vlákno rotoval několik cyklů, dokud se zámek uvolní. Otáčením se vlákno nezablokuje, což je proces náročný na procesor. SpinLock přestane se otáčet za určitých podmínek, aby se zabránilo hladovění logických procesorů nebo prioritní inverze v systémech s Hyper-Threadingem.
Tento příklad používá System.Collections.Generic.Queue<T> třídu, která vyžaduje synchronizaci uživatelů pro přístup s více vlákny. Další možností je použít System.Collections.Concurrent.ConcurrentQueue<T>, což nevyžaduje žádné zámky uživatele.
Všimněte si použití false
volání SpinLock.Exit. Tím dosáhnete nejlepšího výkonu. Zadejte true
architektury IA64 pro použití plotu paměti, který vyprázdní vyrovnávací paměti, aby se zajistilo, že zámek je nyní k dispozici pro další vlákna, která se mají zadat.