理解可重啟序列 (Restartable Sequences):互斥鎖 (Mutexes) 與原子操作 (Atomics) 的解析方案

在追求極致效能的過程中,開發者在處理同步問題時經常會遇到瓶頸。傳統工具如互斥鎖 (mutexes) 和原子操作 (atomic operations) 是確保多執行緒間數據完整性的標準做法,但它們也伴隨著代價:額外開銷 (overhead)。隨著核心數量的增加,對這些鎖的競爭可能會成為顯著的瓶頸,導致 CPU 週期浪費並增加延遲。

可重啟序列 (rseq) 代表了 Linux 核心處理臨界區 (critical sections) 方式的範式轉移。rseq 並非依賴笨重的鎖機制,而是允許程式執行一段短暫的指令序列,如果發生搶佔 (preemption),核心可以安全地中斷並重啟該序列,有效地為使用者空間的記憶體更新提供了一種「事務性」(transactional) 的方法。

可重啟序列如何運作

rseq 的核心設計旨在處理臨界區期間的搶佔問題。在典型情境下,如果一個執行緒在持有鎖的同時被搶佔,其他執行緒可能會進行自旋 (spin) 或阻塞 (block),從而浪費資源。透過可重啟序列,核心可以得知執行緒即將進入的臨界區。

如社群討論中所述,其機制運作如下:

"當你的程式進入一段不希望被中斷的程式碼臨界區時,你會告知核心... 第一條組合語言指令應該是一個設置 rseq_cs 欄位的 move 指令。最後一條指令則需要是執行對全域數據結構修改的操作。"

這個過程建立了一個微小的使用者空間事務。使用者空間應用程式與核心之間的通訊是雙向的,並透過共享記憶體進行,這消除了在臨界區期間進行昂貴系統呼叫 (system calls) 的需求。如果核心搶佔了該執行緒,或者該執行緒被遷移到另一個 CPU,核心會偵測到 rseq_cs 欄位已被設置,並自動將指令指標 (instruction pointer) 重置到序列的開頭,確保操作相對於執行緒的執行是原子性地重啟並完成的。

效能優勢:超越原子操作

關於 rseq 最具啟發性的說法之一是,它甚至可以超越 CPU 內部的原子操作。雖然原子操作比互斥鎖更快,但它們仍然需要在核心之間進行快取行同步 (cache line synchronization),這在高併發環境下可能非常昂貴。

透過使用 rseq,開發者可以潛在於某些熱點路徑 (hot paths) 中移除互斥鎖和原子操作。由於核心管理著重啟邏輯,應用程式可以執行簡單的 loadstore 操作。如果沒有發生搶佔,程式碼會以原始記憶體存取的速度運行。如果發生了搶佔,代價僅是重啟一段非常短的指令序列(通常為 10 條或更少),這比競爭鎖的開銷要高效得多。

實際應用與工具

雖然底層機制涉及組合語言和共享記憶體結構,但對大多數開發者而言,並不需要從頭開始實作 rseq。生態系統已經演進,提供了更高層次的抽象。

對於希望將此技術整合到專案中的人,librseq 函式庫(由 rseq 實作者維護)為常見的使用案例(如計數器和鏈結串列)提供了必要的輔助工具。這讓開發者能夠從可重啟序列中獲取效能優勢,而無需為每個臨界區編寫自定義的組合語言。

考量因素與權衡

儘|n儘管有效能增益,rseq 並非萬靈丹。它特別針對非常短的臨界區進行了優化。如果序列過長,搶佔的機率就會增加,導致頻繁的重啟,進而可能降低效能。

此外,有些開發者認為 rseq 的優勢在於核心數極大的環境中表現最為顯著。在應用程式開發者對少量執行緒擁有完全控制權的情境下,rseq 與傳統的執行緒本地存儲 (thread-local storage) 或優化的原子操作之間的差距可能會較小。然而,隨著硬體持續向數百個核心規模擴展,能夠隨著 CPU 核心而非僅僅隨著執行緒進行擴展的能力,成為了一項關鍵優勢。

結論

可重啟序列透過將「原子性」的責任從硬體(原子操作)或作業系統排程器(互斥鎖)轉移到兩者之間的協作關係,提供了一種精密的方式來減少同步開銷。透過藉由共享記憶體將臨界區視為可重啟的事務,Linux 提供了一條通往高計算效能領域中真正線性擴展的道路。

Sources