記一次.NET 某安全生產系統 CPU爆高分析

一:背景

1.講故事

今天是🐏的第四天,頭終于不巨疼了,寫文章已經沒什么問題,趕緊爬起來寫。

這個月初有位朋友找到我,說他的程序出現了CPU爆高,讓我幫忙看下怎么回事,簡單分析了下有兩點比較有意思。

  1. 這是一個安全生產的信息管理平臺,第一次聽說,我的格局小了。

  2. 這是一個經典的 CPU 爆高問題,過往雖有分析,但沒有刨根問底,剛好這一篇就來問一下底吧。

話不多說,我們上 WinDbg 說話。

二:WinDbg 分析

1. 真的 CPU 爆高嗎?

別人說爆高不算,我們得拿數據說話不是,驗證命令就是 !tp

0:085>?!tp
CPU?utilization:?100%
Worker?Thread:?Total:?40?Running:?26?Idle:?6?MaxLimit:?32767?MinLimit:?8
Work?Request?in?Queue:?0
--------------------------------------
Number?of?Timers:?0
--------------------------------------
Completion?Port?Thread:Total:?1?Free:?1?MaxFree:?16?CurrentLimit:?1?MaxLimit:?1000?MinLimit:?8

從卦中看果然是被打滿了,接下來可以用 ~*e !clrstack 觀察各個線程都在做什么,稍微一觀察就會發現有很多的線程卡在 FindEntry() 方法上,截圖如下:

326376447057e933d830f639f9aea026.png

從圖中可以看到,有 25 個線程都停在 FindEntry() 之上,如果你的經驗比較豐富的話,我相信你馬上就知道這是多線程環境下使用了非線程安全集合 Dictionary 造成的死循環,把 CPU 直接打爆。

按以往套路到這里就結束了,今天我們一定要刨到底。

2. 為什么會出現死循環

要知道死循環的成因,那就一定要從 FindEntry 上入手。

private?int?FindEntry(TKey?key)
{if?(key?==?null){ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);}if?(buckets?!=?null){int?num?=?comparer.GetHashCode(key)?&?0x7FFFFFFF;for?(int?num2?=?buckets[num?%?buckets.Length];?num2?>=?0;?num2?=?entries[num2].next){if?(entries[num2].hashCode?==?num?&&?comparer.Equals(entries[num2].key,?key)){return?num2;}}}return?-1;
}

仔細觀察上面的代碼,如果真有死循環肯定是在 for 中出不來,如果是真的出在 for 上,那問題自然在 next 指針上。

關于 Dictionary 的內部布局和解析 可以參見我的 高級調試訓練營,這里我們就不細說了。

那是不是出在 next 指針上呢?我們來剖析下方法上下文。

3. 觀察 next 指針布局

為了方便觀察,先切到 85 號線程。

0:085>?~85s
mscorlib_ni!System.Collections.Generic.Dictionary<string,F2.xxx.ORM.SqlEntity>.FindEntry+0x8f:
00007ff8`5f128ccf?488b4e10????????mov?????rcx,qword?ptr?[rsi+10h]?ds:0000017f`39c07d00=0000017eb9ee00c0
0:085>?!clrstack
OS?Thread?Id:?0x4124?(85)Child?SP???????????????IP?Call?Site
0000007354ebcc70?00007ff85f128ccf?System.Collections.Generic.Dictionary`2[[System.__Canon,?mscorlib],[System.__Canon,?mscorlib]].FindEntry(System.__Canon)?[f:\dd\ndp\clr\src\BCL\system\collections\generic\dictionary.cs?@?305]

接下來把 Dictionary 中的 Entry[] 中的 next 給展示出來,可以用 !mdso 命令。

0:085>?!mdso
Thread?85:
Location??????????Object????????????Type
------------------------------------------------------------
RCX:??????????????0000017eb9ee00c0??System.Collections.Generic.Dictionary`2+Entry[[System.String,?mscorlib],[xx]][]
RSI:??????????????0000017f39c07cf0??System.Collections.Generic.Dictionary`2[[System.String,?mscorlib],[xxx.xxx]]0:085>?!mdt?-e:2?0000017eb9ee00c0
0000017eb9ee00c0?(System.Collections.Generic.Dictionary`2+Entry[[System.String,?mscorlib],[xxx.xxx]][],?Elements:?3,?ElementMT=00007ff816cedc18)
[0]?(System.Collections.Generic.Dictionary`2+Entry[[System.String,?mscorlib],[F2.xxx]])?VALTYPE?(MT=00007ff816cedc18,?ADDR=0000017eb9ee00d0)hashCode:0x0?(System.Int32)next:0x0?(System.Int32)key:NULL?(System.__Canon)value:NULL?(System.__Canon)
[1]?(System.Collections.Generic.Dictionary`2+Entry[[System.String,?mscorlib],[F2.xxx]])?VALTYPE?(MT=00007ff816cedc18,?ADDR=0000017eb9ee00e8)hashCode:0x5aba4760?(System.Int32)next:0xffffffff?(System.Int32)key:0000017f39c0ab50?(System.String)?Length=20,?String="xxxMessage_Select"value:0000017f39c0b5d0?(xxx.xxx.ORM.SqlEntity)
[2]?(System.Collections.Generic.Dictionary`2+Entry[[System.String,?mscorlib],[F2.xxx]])?VALTYPE?(MT=00007ff816cedc18,?ADDR=0000017eb9ee0100)hashCode:0x65b6e27b?(System.Int32)next:0x1?(System.Int32)key:0000017f39c09d58?(System.String)?Length=20,?String="xxxMessage_Insert"value:0000017f39c0ba50?(xxx.xxx.ORM.SqlEntity)

從卦中看也蠻奇葩的,只有三個元素的 Dictionary 還能死循環。。。如果你仔細觀察會發現 [0] 項是一種有損狀態,value 沒值不說, next:0x0 可是有大問題的,它會永遠指向自己,因為 next 是指向 hash 掛鏈中的下一個節點的數組下標,畫個圖大概是這樣。

baf3aa9f3397a00d35febf02fb893404.png

接下來我們驗證下是不是入口參數不幸進入了 [0] 號坑,然后在這個坑中永遠指向自己呢?要想尋找答案,只需要在 FindEntry 的匯編代碼中找到 int num = comparer.GetHashCode(key) & 0x7FFFFFFF; 中的 num 值,看它是不是 0 即可。

0:085>?!U?/d?00007ff85f128ccf
preJIT?generated?code
System.Collections.Generic.Dictionary`2[[System.__Canon,?mscorlib],[System.__Canon,?mscorlib]].FindEntry(System.__Canon)
Begin?00007ff85f128c40,?size?130.?Cold?region?begin?00007ff85ff07ff0,?size?11
...
f:\dd\ndp\clr\src\BCL\system\collections\generic\dictionary.cs?@?303:
00007ff8`5f128c6f?488b5e18????????mov?????rbx,qword?ptr?[rsi+18h]
00007ff8`5f128c73?488b0e??????????mov?????rcx,qword?ptr?[rsi]
00007ff8`5f128c76?488b5130????????mov?????rdx,qword?ptr?[rcx+30h]
00007ff8`5f128c7a?488b2a??????????mov?????rbp,qword?ptr?[rdx]
00007ff8`5f128c7d?4c8b5d18????????mov?????r11,qword?ptr?[rbp+18h]
00007ff8`5f128c81?4d85db??????????test????r11,r11
00007ff8`5f128c84?750f????????????jne?????mscorlib_ni!System.Collections.Generic.Dictionary<string,xxx.SqlEntity>.FindEntry+0x55?(00007ff8`5f128c95)
00007ff8`5f128c86?488d154d2f1800??lea?????rdx,[mscorlib_ni+0x68bbda?(00007ff8`5f2abbda)]
00007ff8`5f128c8d?e8ce44f3ff??????call????mscorlib_ni+0x43d160?(00007ff8`5f05d160)?(mscorlib_ni)
00007ff8`5f128c92?4c8bd8??????????mov?????r11,rax
00007ff8`5f128c95?488bcb??????????mov?????rcx,rbx
00007ff8`5f128c98?488bd7??????????mov?????rdx,rdi
00007ff8`5f128c9b?3909????????????cmp?????dword?ptr?[rcx],ecx
00007ff8`5f128c9d?41ff13??????????call????qword?ptr?[r11]
00007ff8`5f128ca0?8bd8????????????mov?????ebx,eax
00007ff8`5f128ca2?81e3ffffff7f????and?????ebx,7FFFFFFFh
...0:085>???ebx
Evaluate?expression:?957083499?=?00000000`390bef6b0:085>???0n957083499?%?0n3
Evaluate?expression:?0?=?00000000`00000000

從匯編代碼中分析得出,num 是放在 ebx 寄存器上,此時 num=957083499,再 %3 之后就是 0 號坑,大家再結合源代碼,你會發現這里永遠都不會退出,永遠都是指向自己,自然就是死循環了。

3. .NET6 下的補充

前段時間在整理課件時發現在 .NET6 中不再傻傻的死循環,而是在嘗試 entries.Length 次之后還得不到結束的話,強制拋出異常,代碼如下:

internal?ref?TValue?FindValue(TKey?key)
{uint?hashCode2?=?(uint)comparer.GetHashCode(key);int?bucket2?=?GetBucket(hashCode2);Entry[]?entries2?=?_entries;uint?num2?=?0u;bucket2--;while?((uint)bucket2?<?(uint)entries2.Length){reference?=?ref?entries2[bucket2];if?(reference.hashCode?!=?hashCode2?||?!comparer.Equals(reference.key,?key)){bucket2?=?reference.next;num2++;if?(num2?<=?(uint)entries2.Length){continue;}goto?IL_0171;}goto?IL_0176;}return?ref?Unsafe.NullRef<TValue>();
IL_0176:return?ref?reference.value;
IL_0171:ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();goto?IL_0176;
}

可能是 .NET團隊 被這樣的問題咨詢煩了,干脆拋一個異常得了。。。

三:總結

多線程環境下使用線程不安全集合,問題雖然很小白,但還是有很多朋友栽在這上面,值得反思哈,借這一次機會進一步解釋下死循環形成的內部機理。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280092.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280092.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280092.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JDBC 學習筆記(一)—— JDBC 基礎

1. 什么是 JDBC JDBC&#xff0c;Java Database Connectivity&#xff08;Java 數據庫連接&#xff09;&#xff0c;是一組執行 SQL 語句的 Java API。 JDBC&#xff0c;是 Java SE&#xff08;Java Platform, Standard Edition&#xff09;標準的一部分。 Java 程序可以通過 J…

JavaScript享元模式

JavaScript享元模式 通過兩個例子的對比來凸顯享元模式的特點&#xff1a;享元模式是一個為了提高性能(空間復雜度)的設計模式&#xff0c;享元模式可以避免大量非常相似類的開銷。 第一實例&#xff0c;沒有使用享元模式&#xff0c;計算所花費的時間和空間使用程度。 要求為&…

mac屏幕截圖_如何在Mac上拍攝屏幕截圖

mac屏幕截圖On a Mac, you can take screenshots with a few quick keyboard shortcuts. But Mac OS X also includes more powerful screenshot tools, too. Here are some of the many ways you can get a screenshot on OS X. 在Mac上&#xff0c;您可以使用一些快速的鍵盤快…

實現 .Net 7 下的數據庫定時檢查

在軟件開發過程中&#xff0c;有時候我們需要定時地檢查數據庫中的數據&#xff0c;并在發現新增數據時觸發一個動作。為了實現這個需求&#xff0c;我們在 .Net 7 下進行一次簡單的演示。PeriodicTimer .Net 6 中新增了 PeriodicTimer 這個類&#xff0c;它可以用來創建一個定…

新手AS常見問題集錦

開發環境 以前開發android的時候可以使用eclipse&#xff0c;雖然現在也能使用eclipse&#xff0c;但是google已經不再支持使用eclipse開發android了。因為google有了自己的IDE---android studio&#xff0c;這個IDE我自己認為安裝的時候比較方便&#xff0c;唯一的缺點就是在下…

js進階 11-6 jquery如何獲取和設置元素的寬高(jquery多方法)

js進階 11-6 jquery如何獲取和設置元素的寬高&#xff08;jquery多方法&#xff09; 一、總結 一句話總結&#xff1a;jquery里面多是方法啊&#xff0c;比如jquery對象的寬高。所以取值是方法&#xff0c;賦值就是方法里面帶參數。 1、百度富文本編輯器ueditor如何設置寬高&a…

SparseArray代替HashMap

相信大家都明白&#xff0c;手機軟件的開發不同于PC軟件的開發&#xff0c;因為手機性能相對有限&#xff0c;內存也有限&#xff0c;所謂“寸土寸金”&#xff0c;可能稍有不慎&#xff0c;就會導致性能的明顯降低。Android為了方便開發者&#xff0c;特意在android.util這個包…

也許你曾經讀過他的書

我們愿用“能理能文、才華多元”來形容他。因為熱愛編程和游戲&#xff0c;所以他將愛好變成了職業&#xff0c;并在這條路上持續奔跑&#xff1b;因為熱愛分享&#xff0c;所以他堅持在博客上分享技術觀點并出版了關于 Azure、微軟游戲棧的書籍&#xff1b;因為熱愛挑戰&#…

python測試框架數據生成工具最全資源匯總

xUnit frameworks 單元測試框架frameworks 框架unittest - python自帶的單元測試庫&#xff0c;開箱即用unittest2 - 加強版的單元測試框架&#xff0c;適用于Python 2.7以及后續版本pytest - 成熟且功能強大的單元測試框架plugincompat - pytest的執行及兼容性插件nosetests -…

t30智能插座怎么設置_如何設置ConnectSense智能插座

t30智能插座怎么設置If you like the idea of smart outlets, but wish you had one with more than just one receptacle on it, the ConnectSense Smart Outlet is worth looking into. Here’s how to set it up and instantly get double the fun. 如果您喜歡智能插座的想法…

用鏈表和數組實現HASH表,幾種碰撞沖突解決方法

Hash算法中要解決一個碰撞沖突的辦法&#xff0c;后文中描述了幾種解決方法。下面代碼中用的是鏈式地址法&#xff0c;就是用鏈表和數組實現HASH表。 he/*hash table max size*/ #define HASH_TABLE_MAX_SIZE 40/*hash table大小*/ int hash_table_size0;/*.BH----------------…

安卓操作sqlite3,增刪改查

創建 layout <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:layout_width"match_parent"android:layo…

基于.NetCore開發博客項目 StarBlog - (23) 文章列表接口分頁、過濾、搜索、排序

1前言上一篇留的坑&#xff0c;火速補上。在之前的第6篇中&#xff0c;已經有初步介紹&#xff0c;本文做一些補充&#xff0c;已經搞定這部分的同學可以快速跳過&#xff0c;基于.NetCore開發博客項目 StarBlog - (6) 頁面開發之博客文章列表對標準的WebApi來說&#xff0c;分…

如何在Chrome中保存您當前的所有標簽,以便以后閱讀

Chrome allows you to open tabs from your last browsing session when you open the browser. However, what if you want to save your current set of tabs to re-open at any time? Chrome doesn’t provide a way to do that natively, but there is an easy workaround…

ubuntu 16.04(Windows 10雙系統+grub引導)無法進入tt1~tt6(NVIDIA驅動安裝相關-黑屏,login loop,分辨率)...

目錄 前言回顧最終解決&#xff1a;0.關閉x服務1.禁用nouveau2.加入3.更新4.查找匹配驅動5.選擇推薦版本6.等待安裝后重啟,nvidia-smi查看是否安裝成功,或者lsmod | grep nvidia&#xff0c;成功結果如下7.重啟x服務8.此時還不能進入圖形界面&#xff0c;因為nomodeset還在&…

(備忘)打開office2010總是在配置進度

1、同時按上鍵盤上面的windows鍵和R鍵&#xff0c;出現“運行” 2、輸入“regedit”&#xff0c;回車進入注冊表 3、點擊“HKEY_CURRENT_USER”展開&#xff0c;依次“Software”--“Microsoft”--“Office”--"14.0"--"Word"展開&#xff0c;點擊"Op…

java、oracle對CLOB處理

oracle CLOB字段轉換位VARCHAR 1.實際上處理CLOB字段的時候&#xff0c;直接TO_CHAR&#xff0c;當長度超過4000的時候&#xff0c;會報錯&#xff0c;提示列被截取&#xff1b; CLOB轉varchar2&#xff1a;select to_char(CLOB字段) from table 2.直接使用SUBSTR對CLOB字段進行…

android 更改軟鍵盤_如何在Android的Google鍵盤上更改聲音和振動

android 更改軟鍵盤Tactile feedback from a touch screen keyboard is crucial, in my opinion, but I don’t like sounds when I tap keys. You may not be like me—maybe sounds are your thing, but vibration is annoying. Or maybe you dislike both (you rebel!). The…

『 再看.NET7』看看required屬性有什么不同

還是先看看C#中屬性的這定義&#xff0c;在初始化和訪問上有哪些方式&#xff0c;就能看出required屬性有什么不一樣的地方了。屬性&#xff0c;是封裝字段的&#xff0c;通過get和set訪問器可以很好地驗證數據的有效性。public record Order_00 {public Guid Id { get; set; }…

知識點:Mysql 索引原理完全手冊(1)

知識點&#xff1a;Mysql 索引原理完全手冊(1) 知識點&#xff1a;Mysql 索引原理完全手冊(2) 知識點&#xff1a;Mysql 索引優化實戰(3) 知識點&#xff1a;Mysql 數據庫索引優化實戰(4) Mysql-索引原理完全手冊 一、 介紹二、 索引的原理三、 索引的數據結構四、 聚集索引與輔…