在此之前項目有發生過兩次類似的狀況,都得以解決,但最近又會發現偶爾CPU會跑滿,雖然之前使用過WinDbg解決過兩次問題但人的記憶是不可靠的,今天處理同樣問題的時候還是遇到了一些障礙,這一次希望可以記錄的更全面些。
上兩次的博文鏈接:記一次w3wp占用CPU過高的解決過程(Dictionary和線程安全)、EntityFramework中的線程安全,又是Dictionary。
首先請大家不要噴我,因為這一次還是關於Dictionary的一些低級錯誤,我自己看到都無語了。。。
抓取Dump
使用任務管理器抓取Dump,如果操作系統較低可以使用“Process Explorer”。
使用WinDbg分析
1.使用WinDbg打開dump文件。
2.加載sos.dll
命令:.loadby sos clr
3.查看相關線程信息
命令:!threads –special
special參數會將由CLR創建的特殊線程單獨列出便於減少線程的排查工作。
紅框圈出的是我們要重點排查的線程(工作者線程),至於其它的則是一些CLR自擁有的一些線程,如:GC線程、對象釋放線程、計時器線程、I/O線程等。
線程類型的名稱翻譯:
- GC:垃圾回收線程
- Finalizer:對象釋放線程,.Net至少有一個,用於專門處理對象釋放。
- Timer:計時器線程
- ThreadpoolWorker:工作者線程
- IOCompletion:I/O線程
4.查看具體線程堆棧
命令:~{ThreadId}s、!clrstack
~{ThreadId}s:將當前上下文切換到指定的線程內
!clrstack:得到當前的線程的堆棧信息
第二個紅框的前兩句太長了,我復制在下面:
000000d784afe180 000007fda1efa328 System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Collections.Generic.KeyValuePair`2[[System.__Canon, mscorlib],[System.Boolean, mscorlib]], mscorlib]].FindEntry(System.__Canon)
000000d784afe1f0 000007fda1ef96eb System.Collections.Generic.Dictionary`2[[System.__Canon, mscorlib],[System.Collections.Generic.KeyValuePair`2[[System.__Canon, mscorlib],[System.Boolean, mscorlib]], mscorlib]].TryGetValue(System.__Canon, System.Collections.Generic.KeyValuePair`2<System.__Canon,Boolean> ByRef)
可以發現,是在TryGetValue方法時堵塞了,而看到紅框中的最后一句則可以發現是EnumParseCacheHelper的Parse方法出了問題,這個方法主要是對枚舉轉換的一個緩存處理以提升性能。
為了再次確認問題,我繼續對19、20、21、24等線程進行了查看,都是在這里堵塞了,那么問題浮出水面了,下面就去看代碼,並且解決它。
解決問題
找到對應的代碼:
問題顯而易見,CacheDictionary是一個全局靜態的字段,而我在下面方法使用它的時候絲毫沒有注意並發下的情況,沒有加鎖來保證線程安全。。看到這感覺不可思議怎么犯這么低級的錯誤。。。
解決它方式:
解決方式很簡單,使用了.NET4提供的線程安全的字典:ConcurrentDictionary。
關於這一次問題的思考
Dictionary為什么這么容易堵塞
這邊引用之前的博文內容:
我知道Dictionary不是一個線程安全的類型,但我原本以為Dictionary在非線程安全方式下訪問時數據會錯亂,而不會堵塞或者死鎖,而這次的這個問題讓我感覺到訝異,為什么Add一個項目會造成堵塞?
反編譯Dictionary的源碼后發現異常的復雜,也沒有細究,所以下面的一段描述大家抱有自己的想法去閱讀,可能是錯的也可能是對的。
上面是我認為存在問題的地方,當一個線程執行過Initialize后buckets數組的值被修改,而第二個線程同時進入了Initialize方法,那么第一個線程所維護的值被破壞,造成在算法環節出現了死循環,這也可以說明了為什么cpu有時候是50%有時候是99%的問題。
當前有多少個線程發生了這種狀態,如果發生這種狀態的線程越多則代表cpu占用越多。
這次問題的經驗:以后在使用集合或字典時首先應該先想到System.Collections.Concurrent命名空間,雖然它的性能在正常情況下低於普通的Dictionary,但那么幾十或者幾百毫秒的損失對於穩定性來說微不足道,也減少了問題的發生。
寫在最后
因為Rabbit.WeiXin是一個開源項目當然第一件事情就是發布更新。。避免更多的人出現此問題。
交流方式
QQ群:384413261(RabbitHub)
Email:majian159@live.com










