Workstation 技術簡報:為什麼 Rust 仍是許多現代應用(API、邊緣、智慧體、CI/CD 平臺)的強預設選擇,以及如何在非同步執行時旁正確放置 CPU-bound 工作。我們從 Alice Ryhl 的文章 Async: What is blocking? 獲得啟發(非逐字轉載),尤其是 Rayon 相關指導。配套:部落格 · 證據:Polyglot Benchmarks · 相關:多語言文章。
- 阻塞(非同步含義):阻止執行時切換任務——通常是長時間不觸及
.await。 - CPU-bound 解法:優先 Rayon(或專用執行緒),不要把重計算塞進 Tokio worker。
- IO-bound 同步庫:
tokio::task::spawn_blocking通常是正確的池。 - 多語言:Rust 是同行中的強工具之一——用 Polyglot Benchmarks 測量後再強制選型。
- 致謝:概念框架受 Alice Ryhl / ryhl.io 啟發;Workstation 將其落到平臺工程。
1. 生產環境中真正重要的 Rust 優勢
現代應用組合很少是單一語言。但某些表面特別獎勵 Rust 的特性:
- 無 GC 抖動的記憶體安全 — 所有權與借用在編譯期捕獲 use-after-free 與資料競爭;延遲敏感路徑避免 stop-the-world。
- 可預期的效能 — 零成本抽象與精細分配控制,使 Rust 在熱 API 路徑與邊緣變換上具有競爭力。
- 有結構的無畏併發 — Send/Sync 與非同步生態(Tokio、async-trait 模式、channel)鼓勵可擴充套件的扇出設計。
- 可部署二進位制 — 近似靜態產物簡化閘道器、智慧體與 CI/CD sidecar 容器。
- 多語言莊園中的互操作 — FFI、Wasm 與 HTTP 讓 Rust 與 Go、Python、JVM、Lua 邊緣共存,無需重寫一切。
Workstation 的立場與我們的 Polyglot Benchmarks 部落格 和 長文 一致:為有界上下文選擇合適工具。在 polyglot-benchmarks.fictionally.org 實時儀表板上,Rust(該 harness 中的 Actix)常在 CPU-bound 與併發敏感 HTTP 行上表現強勁——這是 ADR 論證熱路徑用 Rust 的證據,而不是信仰。
2. 協作式排程:“阻塞”的含義
Async Rust 使用協作式排程。執行時在任務到達 .await 時切換。Alice Ryhl 那句難忘的規則適用於我們交付的所有非同步服務:
非同步程式碼絕不應長時間不觸及 .await。
在此詞彙中,“阻塞執行緒”不僅僅是“做 IO”。它意味著阻止執行時切換當前任務。經典踩坑:
- 在 async fn 內使用
std::thread::sleep(無 await — 在join!下計時器序列)。 - 在 Tokio worker 上跑重迴圈、壓縮、密碼學、向量運算或重度 JSON。
- 在非同步池上跨長臨界區持有同步互斥鎖(短鎖可以;長鎖不行)。
多執行緒執行時可能把 bug 藏到 worker 飽和為止。生產流量會替你發現它。對延遲 SLO,把 await 之間數十到數百微秒當作協作預算;更長的工作應離開非同步池。
3. 必須阻塞時的三種去處
當你有意需要阻塞——昂貴 CPU 或同步 IO——把工作移出 Tokio 排程執行緒。速查表(對齊 Ryhl 的框架):
| 方法 | CPU-bound | 同步 IO | 永不退出 |
|---|---|---|---|
spawn_blocking | 次優(大池) | OK | 否 |
| Rayon | OK | 否 | 否 |
專用 std::thread | OK | OK | OK |
3.1 同步 IO 用 spawn_blocking
tokio::task::spawn_blocking 排程到 Tokio 阻塞池(預設可達數百執行緒)。適合檔案系統與阻塞資料庫驅動。對持續 CPU 不佳,因為過訂閱會與 OS 排程器對抗——少量短計算可以,作為並行重算預設則危險。
3.2 昂貴 CPU 用 Rayon
Rayon 維護按 CPU 規模的並行池。關鍵整合細節:不要在等待 Rayon 時阻塞 Tokio worker。在 Rayon 上 spawn,透過 tokio::sync::oneshot 回傳結果,在非同步側 .await 接收端。並行迭代器(par_iter)仍需要外層 rayon::spawn,因為它們會阻塞到完成。
// Shape only — see ryhl.io for a full walkthrough
async fn parallel_work(data: Vec<i32>) -> i32 {
let (tx, rx) = tokio::sync::oneshot::channel();
rayon::spawn(move || {
let sum: i32 = data.into_iter().sum(); // or par_iter inside
let _ = tx.send(sum);
});
rx.await.expect("rayon task panicked")
}
致謝:該整合模式是 ryhl.io 上 Rayon crate 章節的核心。Workstation 建議在產品服務中採用同樣形狀,使請求執行緒保持可排程。
3.3 永不退出的工作用專用執行緒
永不退出的迴圈(專用 DB 連線所有者、長壽命橋接)不應永久佔用任一池的槽位。優先 std::thread::spawn,並用 channel 通訊。
4. 對映到現代應用型別
| 表面 | 留在非同步 | 解除安裝 |
|---|---|---|
| APIs | 接受、認證、扇出 HTTP、流式 | 重序列化、密碼學批處理、打分 |
| 邊緣 / 閘道器 | 路由、快取查詢、WAF 決策 | 偶發 CPU 變換;測得更好時優先 Lua/njs |
| 智慧體 | 工具編排、MCP 會話、超時 | 嵌入準備、評測套件、大型本地變換 |
| CI/CD 平臺 | 晉升 API、健康輪詢、UI/API | 深度產物分析、批次校驗 |
Workstation 產品體現這種拆分:Ring Promoter 必須保持晉升與健康門快速;WSL Proxy 保持邊緣路徑空閒;KubePilot 即使分析很重也需要靈敏的事故迴圈。Rust(或 Go、或 Lua)按表面測量後選擇——絕不是走廊辯論的贏家。
5. 採用 async Rust 的架構清單
- 盤點 await 缺口:永不讓步的 profiler 與 tracing span。
- 分類阻塞工作:同步 IO vs CPU vs 永不退出迴圈。
- 選擇池:
spawn_blocking、Rayon + oneshot,或專用執行緒。 - 用真實併發做負載測試——多執行緒執行時在 N=1 時會藏 bug。
- 在 ADR 中記錄決策;涉及語言選型時附上 Polyglot Benchmarks 行。
- 重讀 Tokio 關於共享狀態與協作讓步對尾延遲的指導。
6. 延伸閱讀
- Alice Ryhl — Async: What is blocking?(Rayon / spawn_blocking 框架的主要靈感)。
- Workstation — Polyglot Benchmarks 實時儀表板。
- Workstation — Polyglot 部落格 · Polyglot 文章。
- Workstation — 配套部落格,本簡報的速覽版。
由 Workstation 釋出。概念致謝 Alice Ryhl 關於非同步阻塞與 Rayon 的公開寫作;產品框架與多語言指導均為 Workstation 原創。