沒有開發者樂於處理線程同步,稍有不慎便導致共享資源狀態不一致,且同步代碼往往拖慢性能。在多核時代,如何最小化同步開銷至關重要。
Critical Section(.NET 稱 Monitor)先在用戶態自旋,實在不行才進內核。現實中線程數遠超核心數(單核機常見 300+ 線程),原因包括進程隔離、CLR 專用 GC 線程、開發者濫用線程而非異步 I/O 等。若你在詢問「最大線程數」、「線程池上限」等問題,架構大概率需重構。
Critical Section 進入流程:
步驟二的自旋即 SpinWait,僅在 SMP/多核有意義;單核自旋純屬浪費。
public struct SpinWait
{
internal const int yieldFrequency = 4000;
private volatile int count;
public void Spin()
{
this.count = ++count % Int32.MaxValue;
if (IsSingleProcessor)
{
SwitchToThread();
}
else
{
int n = this.count % yieldFrequency;
if (n > 0)
Thread.SpinWait((int)(1f + (n * 0.032f)));
else
SwitchToThread();
}
}
public void Reset() { this.count = 0; }
public int Count { get { return this.count; } }
private static readonly bool IsSingleProcessor = (Environment.ProcessorCount == 1);
[DllImport("Kernel32", ExactSpelling = true)]
private static extern void SwitchToThread();
}
SwitchToThread() 讓出時間片,優於 Sleep(0)(避免優先級反轉、性能更好)。Thread.SpinWait() 執行 PAUSE/YieldProcessor 指令,解決超線程邏輯核心共享執行資源導致的管線停滯,Intel 報稱可提升 10~30% 性能。yieldFrequency=4000 參考 Windows 臨界區自旋計數,經驗值 4000~12000,需實測調優。線程上下文切換成本設為 C。若等待時間 < 2C(1C 等待 + 1C 喚醒),自旋比阻塞更划算,降低系統開銷與延遲,提升吞吐與伸縮性。
Thread.SpinWait() 優化超線程。結合前文 Lock-Free 棧與 SpinWait,構建高性能並發棧:
public class ConcurrentStack<T>
{
private volatile ListNode<T> topOfStack;
public void Push(T item)
{
var newTop = new ListNode<T>(item);
var wait = new SpinWait();
while (true)
{
var oldTop = topOfStack;
newTop.next = oldTop;
if (Interlocked.CompareExchange<ListNode<T>>(ref topOfStack, newTop, oldTop) == oldTop)
return;
wait.Spin();
}
}
public bool TryPop(out T result)
{
var wait = new SpinWait();
Spin:
var oldTop = topOfStack;
if (oldTop == null) { result = default(T); return false; }
var newTop = oldTop.next;
if (Interlocked.CompareExchange<ListNode<T>>(ref topOfStack, newTop, oldTop) != oldTop)
{
wait.Spin();
goto Spin;
}
result = oldTop.item;
return true;
}
// TryPeek、ListNode 略
}
goto 此處用於簡化重試邏輯,配合 SpinWait 實現高效自旋。重申:SpinWait 仅适用于等待时间短、SMP/多核环境。