Postupy: Použití SpinLocku pro synchronizaci nízké úrovně
Následující příklad ukazuje, jak použít SpinLock. V tomto příkladu kritická část provádí minimální množství práce, což z ní činí vhodného kandidáta na SpinLock. Mírné zvýšení práce zvyšuje 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: {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: {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 držet příliš dlouho. V takových případech může být ve více-jádrových počítačích efektivní, aby blokované vlákno čekalo několik cyklů, dokud se zámek neuvolní. Otáčením se vlákno nezablokuje, což je proces náročný na procesor. SpinLock se za určitých podmínek přestane točit, aby se zabránilo hladovění logických procesorů nebo inverzi priority v systémech s hyper-threadingem.
Tento příklad používá třídu System.Collections.Generic.Queue<T>, 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>, které nevyžaduje žádné zámky uživatele.
Všimněte si použití false
ve volání SpinLock.Exit. Tím dosáhnete nejlepšího výkonu. Zadejte true
na architekturách IA64 pro použití paměťové bariéry, která vyprázdní zápisové vyrovnávací paměti, aby bylo zajištěno, že zámek je nyní dostupný pro další vlákna, která mohou vstoupit.
Viz také
- Objekty a vlastnosti threadingu
- příkazu lock (C#)
- Příkaz SyncLock (Visual Basic)