Nasıl yapılır: İki Aşamalı bir Bekleme İşlemini Uygulamak için SpinWait Kullanma
Aşağıdaki örnekte, iki aşamalı bekleme işlemi uygulamak için bir nesnenin nasıl kullanılacağı System.Threading.SpinWait gösterilmektedir. İlk aşamada, eşitleme nesnesi olan bir Latch
, kilidin kullanılabilir olup olmadığını denetlerken birkaç döngü için döner. İkinci aşamada, kilit kullanılabilir hale gelirse, Wait
yöntem beklemesini gerçekleştirmek için öğesini kullanmadan System.Threading.ManualResetEvent geri döner; aksi takdirde Wait
beklemeyi gerçekleştirir.
Örnek
Bu örnekte, bir Mandal eşitleme temel öğesinin çok temel bir uygulaması gösterilmektedir. Bekleme sürelerinin çok kısa olması beklendiğinde bu veri yapısını kullanabilirsiniz. Bu örnek yalnızca tanıtım amaçlıdır. Programınızda mandal türü işlevselliğine ihtiyacınız varsa kullanmayı System.Threading.ManualResetEventSlimgöz önünde bulundurun.
#define LOGGING
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
class Latch
{
private object latchLock = new object();
// 0 = unset, 1 = set.
private int m_state = 0;
private volatile int totalKernelWaits = 0;
// Block threads waiting for ManualResetEvent.
private ManualResetEvent m_ev = new ManualResetEvent(false);
#if LOGGING
// For fast logging with minimal impact on latch behavior.
// Spin counts greater than 20 might be encountered depending on machine config.
private long[] spinCountLog = new long[20];
public void DisplayLog()
{
for (int i = 0; i < spinCountLog.Length; i++)
{
Console.WriteLine("Wait succeeded with spin count of {0} on {1:N0} attempts",
i, spinCountLog[i]);
}
Console.WriteLine("Wait used the kernel event on {0:N0} attempts.", totalKernelWaits);
Console.WriteLine("Logging complete");
}
#endif
public void Set()
{
lock(latchLock) {
m_state = 1;
m_ev.Set();
}
}
public void Wait()
{
Trace.WriteLine("Wait timeout infinite");
Wait(Timeout.Infinite);
}
public bool Wait(int timeout)
{
SpinWait spinner = new SpinWait();
Stopwatch watch;
while (m_state == 0)
{
// Lazily allocate and start stopwatch to track timeout.
watch = Stopwatch.StartNew();
// Spin only until the SpinWait is ready
// to initiate its own context switch.
if (!spinner.NextSpinWillYield)
{
spinner.SpinOnce();
}
// Rather than let SpinWait do a context switch now,
// we initiate the kernel Wait operation, because
// we plan on doing this anyway.
else
{
Interlocked.Increment(ref totalKernelWaits);
// Account for elapsed time.
long realTimeout = timeout - watch.ElapsedMilliseconds;
// Do the wait.
if (realTimeout <= 0 || !m_ev.WaitOne((int)realTimeout))
{
Trace.WriteLine("wait timed out.");
return false;
}
}
}
#if LOGGING
Interlocked.Increment(ref spinCountLog[spinner.Count]);
#endif
// Take the latch.
Interlocked.Exchange(ref m_state, 0);
return true;
}
}
class Example
{
static Latch latch = new Latch();
static int count = 2;
static CancellationTokenSource cts = new CancellationTokenSource();
static void TestMethod()
{
while (!cts.IsCancellationRequested)
{
// Obtain the latch.
if (latch.Wait(50))
{
// Do the work. Here we vary the workload a slight amount
// to help cause varying spin counts in latch.
double d = 0;
if (count % 2 != 0) {
d = Math.Sqrt(count);
}
Interlocked.Increment(ref count);
// Release the latch.
latch.Set();
}
}
}
static void Main()
{
// Demonstrate latch with a simple scenario: multiple
// threads updating a shared integer. Both operations
// are relatively fast, which enables the latch to
// demonstrate successful waits by spinning only.
latch.Set();
// UI thread. Press 'c' to cancel the loop.
Task.Factory.StartNew(() =>
{
Console.WriteLine("Press 'c' to cancel.");
if (Console.ReadKey(true).KeyChar == 'c') {
cts.Cancel();
}
});
Parallel.Invoke( () => TestMethod(),
() => TestMethod(),
() => TestMethod() );
#if LOGGING
latch.DisplayLog();
if (cts != null) cts.Dispose();
#endif
}
}
#Const LOGGING = 1
Imports System.Diagnostics
Imports System.Threading
Imports System.Threading.Tasks
Class Latch
Private latchLock As New Object()
' 0 = unset, 1 = set.
Private m_state As Integer = 0
Private totalKernelWaits As Integer = 0
' Block threads waiting for ManualResetEvent.
Private m_ev = New ManualResetEvent(False)
#If LOGGING Then
' For fast logging with minimal impact on latch behavior.
' Spin counts greater than 20 might be encountered depending on machine config.
Dim spinCountLog(19) As Long
Public Sub DisplayLog()
For i As Integer = 0 To spinCountLog.Length - 1
Console.WriteLine("Wait succeeded with spin count of {0} on {1:N0} attempts",
i, spinCountLog(i))
Next
Console.WriteLine("Wait used the kernel event on {0:N0} attempts.",
totalKernelWaits)
Console.WriteLine("Logging complete")
End Sub
#End If
Public Sub SetLatch()
SyncLock (latchLock)
m_state = 1
m_ev.Set()
End SyncLock
End Sub
Public Sub Wait()
Trace.WriteLine("Wait timeout infinite")
Wait(Timeout.Infinite)
End Sub
Public Function Wait(ByVal timeout As Integer) As Boolean
' Allocated on the stack.
Dim spinner = New SpinWait()
Dim watch As Stopwatch
While (m_state = 0)
' Lazily allocate and start stopwatch to track timeout.
watch = Stopwatch.StartNew()
' Spin only until the SpinWait is ready
' to initiate its own context switch.
If Not spinner.NextSpinWillYield Then
spinner.SpinOnce()
' Rather than let SpinWait do a context switch now,
' we initiate the kernel Wait operation, because
' we plan on doing this anyway.
Else
Interlocked.Increment(totalKernelWaits)
' Account for elapsed time.
Dim realTimeout As Long = timeout - watch.ElapsedMilliseconds
' Do the wait.
If realTimeout <= 0 OrElse Not m_ev.WaitOne(realTimeout) Then
Trace.WriteLine("wait timed out.")
Return False
End If
End If
End While
#If LOGGING Then
Interlocked.Increment(spinCountLog(spinner.Count))
#End If
' Take the latch.
Interlocked.Exchange(m_state, 0)
Return True
End Function
End Class
Class Program
Shared latch = New Latch()
Shared count As Integer = 2
Shared cts = New CancellationTokenSource()
Shared lockObj As New Object()
Shared Sub TestMethod()
While (Not cts.IsCancellationRequested)
' Obtain the latch.
If (latch.Wait(50)) Then
' Do the work. Here we vary the workload a slight amount
' to help cause varying spin counts in latch.
Dim d As Double = 0
If (count Mod 2 <> 0) Then
d = Math.Sqrt(count)
End If
SyncLock (lockObj)
If count = Int32.MaxValue Then count = 0
count += 1
End SyncLock
' Release the latch.
latch.SetLatch()
End If
End While
End Sub
Shared Sub Main()
' Demonstrate latch with a simple scenario:
' two threads updating a shared integer and
' accessing a shared StringBuilder. Both operations
' are relatively fast, which enables the latch to
' demonstrate successful waits by spinning only.
latch.SetLatch()
' UI thread. Press 'c' to cancel the loop.
Task.Factory.StartNew(Sub()
Console.WriteLine("Press 'c' to cancel.")
If (Console.ReadKey(True).KeyChar = "c"c) Then
cts.Cancel()
End If
End Sub)
Parallel.Invoke(
Sub() TestMethod(),
Sub() TestMethod(),
Sub() TestMethod()
)
#If LOGGING Then
latch.DisplayLog()
#End If
If cts IsNot Nothing Then cts.Dispose()
End Sub
End Class
Mandalı SpinWait , nesnesini yalnızca bir sonraki çağrıya kadar döndürmek için SpinOnce
kullanır ve iş parçacığının zaman dilimini vermesine neden olur SpinWait . Bu noktada mandal, üzerinde çağrısı WaitOneManualResetEvent yaparak ve zaman aşımı değerinin geri kalanını geçirerek kendi bağlam anahtarına neden olur.
Günlük çıkışı, Mandal'ın kilidi kullanmadan ManualResetEventalarak performansı ne sıklıkta artırdığını gösterir.