再記一次w3wp占用CPU過高的解決過程(Dictionary和線程安全)


在此之前項目有發生過兩次類似的狀況,都得以解決,但最近又會發現偶爾CPU會跑滿,雖然之前使用過WinDbg解決過兩次問題但人的記憶是不可靠的,今天處理同樣問題的時候還是遇到了一些障礙,這一次希望可以記錄的更全面些。

上兩次的博文鏈接:記一次w3wp占用CPU過高的解決過程(Dictionary和線程安全)EntityFramework中的線程安全,又是Dictionary

首先請大家不要噴我,因為這一次還是關於Dictionary的一些低級錯誤,我自己看到都無語了。。。

抓取Dump

使用任務管理器抓取Dump,如果操作系統較低可以使用“Process Explorer”。

image

使用WinDbg分析

1.使用WinDbg打開dump文件。

2.加載sos.dll

命令:.loadby sos clr

image

3.查看相關線程信息

命令:!threads –special

special參數會將由CLR創建的特殊線程單獨列出便於減少線程的排查工作。

image

紅框圈出的是我們要重點排查的線程(工作者線程),至於其它的則是一些CLR自擁有的一些線程,如:GC線程、對象釋放線程、計時器線程、I/O線程等。

線程類型的名稱翻譯:

  • GC:垃圾回收線程
  • Finalizer:對象釋放線程,.Net至少有一個,用於專門處理對象釋放。
  • Timer:計時器線程
  • ThreadpoolWorker:工作者線程
  • IOCompletion:I/O線程

4.查看具體線程堆棧

命令:~{ThreadId}s、!clrstack

~{ThreadId}s:將當前上下文切換到指定的線程內

image

!clrstack:得到當前的線程的堆棧信息

image

第二個紅框的前兩句太長了,我復制在下面:

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等線程進行了查看,都是在這里堵塞了,那么問題浮出水面了,下面就去看代碼,並且解決它。

解決問題

找到對應的代碼:

image

問題顯而易見,CacheDictionary是一個全局靜態的字段,而我在下面方法使用它的時候絲毫沒有注意並發下的情況,沒有加鎖來保證線程安全。。看到這感覺不可思議怎么犯這么低級的錯誤。。。

解決它方式:

image

解決方式很簡單,使用了.NET4提供的線程安全的字典:ConcurrentDictionary。

關於這一次問題的思考

Dictionary為什么這么容易堵塞

這邊引用之前的博文內容:

我知道Dictionary不是一個線程安全的類型,但我原本以為Dictionary在非線程安全方式下訪問時數據會錯亂,而不會堵塞或者死鎖,而這次的這個問題讓我感覺到訝異,為什么Add一個項目會造成堵塞?

反編譯Dictionary的源碼后發現異常的復雜,也沒有細究,所以下面的一段描述大家抱有自己的想法去閱讀,可能是錯的也可能是對的。

image

image

上面是我認為存在問題的地方,當一個線程執行過Initialize后buckets數組的值被修改,而第二個線程同時進入了Initialize方法,那么第一個線程所維護的值被破壞,造成在算法環節出現了死循環,這也可以說明了為什么cpu有時候是50%有時候是99%的問題。

當前有多少個線程發生了這種狀態,如果發生這種狀態的線程越多則代表cpu占用越多。

這次問題的經驗:以后在使用集合或字典時首先應該先想到System.Collections.Concurrent命名空間,雖然它的性能在正常情況下低於普通的Dictionary,但那么幾十或者幾百毫秒的損失對於穩定性來說微不足道,也減少了問題的發生。

寫在最后

因為Rabbit.WeiXin是一個開源項目當然第一件事情就是發布更新。。避免更多的人出現此問題。

image

交流方式

QQ群:384413261(RabbitHub)

Email:majian159@live.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM