한국어English日本語简体中文繁體中文DeutschไทยTiếng ViệtРусскийPortuguês (Brasil)EspañolBahasa Indonesia

遊戲 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)叢集的資源競爭

出處

  1. Amdahl's Law in the Multicore Era IEEE
    IEEE Computer 2008 論文(作者公開版本)。無法平行化的比例為 1−f 時,CPU 核心再怎麼增加,加速倍數也不會超過 1/(1−f)(阿姆達爾定律)
  2. Request scheduling Microsoft
    Orleans 的 grain(actor)採用一次把一個請求處理完的單執行緒執行模型,因此不會同時修改狀態;grain 互相等待對方回應時可能發生死結
  3. pidstat(1) — Linux manual page sysstat
    -w 的 cswch/s 是為了等待資源而停下的自願 context switch 次數;加 -t 可依執行緒顯示
  4. Demonstrations of offcputime, the Linux eBPF/bcc version IO Visor
    把執行緒停住、離開 CPU 的時間(off-CPU)依呼叫堆疊加總,-p 指定處理程序
  5. Well-known EventCounters in .NET Microsoft
    Monitor Lock Contention Count(monitor-lock-contention-count):嘗試取得 monitor 鎖時發生競爭的次數
  6. .NET runtime metrics .NET
    .NET 9 起的 dotnet.monitor.lock_contentions:處理程序啟動後,嘗試取得 monitor 鎖時發生競爭的次數

相關原因

同一層:L9 伺服器遊戲程式

同一症狀(輸入延遲)在其他層的原因

查看含圖解與實驗的完整版卡片