遊戲 Lag 白皮書 › L9 伺服器遊戲程式
鎖競爭 Lock contention
原因 ID sp-lock · 主要負責 遊戲開發團隊(伺服器開發)
在含圖解與實驗的完整版中開啟卡片 →
多個執行緒為了寫入同一份資料而等待同一個鎖時,執行緒再多也只能一次執行一個。
為什麼 拍賣場、公會倉庫這類共用資料由多個執行緒同時使用 → 於是 拿到鎖的執行緒完成之前,其餘執行緒都在等待 → 畫面上 只有特定功能變慢,嚴重時整個 tick 延遲
- 症狀
- 輸入延遲, 定格
- 因素
- 停滯
- 誰會遇到
- 只有特定功能, 整個伺服器
- 何時
- 人潮湧入時
- 負責單位
- 主要負責 遊戲開發團隊(伺服器開發)
- 遊戲開發團隊要做的事
- 把鎖切得更細、減少在鎖內做的事、改用訊息導向的架構(每份資料指定負責的執行緒,其他執行緒只透過訊息送出請求)。
- 數值參考
- 鎖內的工作占整體的 20% 時,執行緒再怎麼增加,處理量最多也只有單一執行緒的 5 倍;占 40% 時停在 2.5 倍。
- 圖表上
- 隨人數/負載上升 · 請求處理時間、各執行緒的 CPU 與 context switch
- 查看位置
- 用 pidstat -w -t 1 看各執行緒的自願 context switch(cswch/s,為等待資源而停下的次數),用 bcc offcputime -p 看執行緒離開 CPU 後在哪裡等待(各呼叫堆疊的等待時間)。.NET 則看 dotnet-counters 的鎖競爭次數(.NET 9 以後為 dotnet.monitor.lock_contentions,8 以前為 Monitor Lock Contention Count)
- 符合的跡象
- 負載增加時 CPU 使用率仍維持在低檔,處理時間卻變長;大部分等待時間集中在嘗試取得鎖的呼叫堆疊,鎖競爭次數也一起上升
- 不符合的跡象
- CPU 滿載時是運算量問題(超出 tick 預算、單執行緒區域過載)。等待的地方是 DB 或檔案呼叫時,是遊戲執行緒上的同步呼叫
- 確認方式
- 用基礎設施工具確認(不需要遊戲程式碼)
- 深入了解
- 這發生在多個執行緒共同修改遊戲資料的架構中。每個區域或功能由一個執行緒負責、只透過訊息溝通的架構幾乎沒有鎖,但要留意工作集中在單一執行緒的問題(單執行緒區域過載)。遊戲執行緒若在等待緩慢的存檔作業所持有的鎖,那整個 tick 都會停住。
- 實際案例
- Roblox 2021: Roblox 73 小時事故:服務探索(Consul)叢集的資源競爭
出處
- Amdahl's Law in the Multicore Era IEEE
IEEE Computer 2008 論文(作者公開版本)。無法平行化的比例為 1−f 時,CPU 核心再怎麼增加,加速倍數也不會超過 1/(1−f)(阿姆達爾定律) - Request scheduling Microsoft
Orleans 的 grain(actor)採用一次把一個請求處理完的單執行緒執行模型,因此不會同時修改狀態;grain 互相等待對方回應時可能發生死結 - pidstat(1) — Linux manual page sysstat
-w 的 cswch/s 是為了等待資源而停下的自願 context switch 次數;加 -t 可依執行緒顯示 - Demonstrations of offcputime, the Linux eBPF/bcc version IO Visor
把執行緒停住、離開 CPU 的時間(off-CPU)依呼叫堆疊加總,-p 指定處理程序 - Well-known EventCounters in .NET Microsoft
Monitor Lock Contention Count(monitor-lock-contention-count):嘗試取得 monitor 鎖時發生競爭的次數 - .NET runtime metrics .NET
.NET 9 起的 dotnet.monitor.lock_contentions:處理程序啟動後,嘗試取得 monitor 鎖時發生競爭的次數
相關原因
同一層:L9 伺服器遊戲程式
同一症狀(輸入延遲)在其他層的原因
查看含圖解與實驗的完整版卡片