Så här använder du SpinLock för synkronisering på låg nivå
I följande exempel visas hur du använder en SpinLock. I det här exemplet utför det kritiska avsnittet en minimal mängd arbete, vilket gör det till en bra kandidat för en SpinLock. Om du ökar arbetet en liten mängd ökar prestandan för det SpinLock jämfört med ett standardlås. Det finns dock en punkt där en SpinLock blir dyrare än ett standardlås. Du kan använda funktionerna för samtidighetsprofilering i profileringsverktygen för att se vilken typ av lås som ger bättre prestanda i ditt program. Mer information finns i Concurrency Visualizer.
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 kan vara användbart när ett lås på en delad resurs inte kommer att hållas under mycket lång tid. I sådana fall kan det på datorer med flera kärnor vara effektivt för den blockerade tråden att snurra några cykler tills låset släpps. Genom att snurra blockeras inte tråden, vilket är en processorintensiv process. SpinLock slutar snurra under vissa förhållanden för att förhindra svält av logiska processorer eller prioriterad inversion på system med Hyper-Threading.
I det System.Collections.Generic.Queue<T> här exemplet används klassen, som kräver användarsynkronisering för åtkomst med flera trådar. Ett annat alternativ är att använda System.Collections.Concurrent.ConcurrentQueue<T>, som inte kräver några användarlås.
Observera användningen av false
i anropet till SpinLock.Exit. Detta ger bästa möjliga prestanda. Ange true
i IA64-arkitekturer att använda minnesstängslet, vilket rensar skrivbuffertarna för att säkerställa att låset nu är tillgängligt för andra trådar att ange.