HOW TO:使用 SpinLock 進行低階同步處理
下列範例示範如何使用 SpinLock。
範例
在上述範例中,關鍵的區段會執行最少量的工作,因此是 SpinLock 很好的候選鎖定目標。 相較於標準鎖定,小幅遞增工作量可以增加 SpinLock 的效能。 但是會有臨界點,超過這個點,SpinLock 就變得比標準鎖定耗費資源。 您可以使用 Visual Studio Team Developer 版程式碼剖析工具中新的並行程式碼剖析功能,查看哪一種鎖定類型會在程式中提供較佳的效能。 如需詳細資訊,請參閱並行視覺化檢視。
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 可以在特定情況下停止空轉作業,以避免耗盡邏輯處理器資源,或是破壞啟用超執行緒 (Hyper-Threading) 之系統上的優先權順序。
上述範例會使用 System.Collections.Generic.Queue<T> 類別,這個類別必須透過使用者同步處理進行多執行緒存取。 在以 .NET Framework 第 4 版 (含) 以後版本為目標的應用程式中有另一種選擇,即是使用 System.Collections.Concurrent.ConcurrentQueue<T>,這就不需要任何使用者鎖定。
請注意 Exit 呼叫中使用的 false (在 Visual Basic 為 False)。 這可提供最佳的效能。 請在 IA64 架構上指定 true (True) 以使用記憶體柵欄,記憶體柵欄會清空寫入緩衝區,以確保其他執行緒現在可以使用鎖定來執行結束。