首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

並發數據結構:SpinWait 自旋等待機制詳解

     編輯:關於.NET

沒有開發者樂於處理線程同步,稍有不慎便導致共享資源狀態不一致,且同步代碼往往拖慢性能。在多核時代,如何最小化同步開銷至關重要。

三大性能守則

  1. 避免用戶態→內核態轉換:Windows 上極其昂貴。Critical Section(.NET 稱 Monitor)先在用戶態自旋,實在不行才進內核。
  2. 避免線程進入等待狀態:停止運行的線程零性能。守則一做到,守則二自然滿足。
  3. 線程數 ≤ CPU 核心數:超過核心數會觸發頻繁上下文切換(Windows ~20ms 時間片,切換需數千周期),純屬開銷。

現實中線程數遠超核心數(單核機常見 300+ 線程),原因包括進程隔離、CLR 專用 GC 線程、開發者濫用線程而非異步 I/O 等。若你在詢問「最大線程數」、「線程池上限」等問題,架構大概率需重構。

SpinWait:用戶態自旋的藝術

Critical Section 進入流程:

  1. 測試鎖位,未鎖則直接置位繼續(1~2 指令)。
  2. 已鎖則進入自旋等待(SMP/多核下),單核立即放棄。
  3. 放棄後用信號量進內核等待。
  4. 釋放鎖時通知內核喚醒等待者。

步驟二的自旋即 SpinWait,僅在 SMP/多核有意義;單核自旋純屬浪費。

SpinWait 實現

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() 優化超線程。
  • 引入輕微退避隨機化,改善局部性、避免活鎖。
  • 嚴禁單核使用。

ConcurrentStack 實戰

結合前文 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/多核环境。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved