zoukankan      html  css  js  c++  java
  • 再记一次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

  • 相关阅读:
    【UML建模】UML类图几种关系的总结
    【架构框架】IoC框架
    【AutoMapper基础】值解析器--Custom value resolvers
    【AutoMapper基础】简单示例--Flattening
    【AutoMapper简介】
    【UML建模】UML类图符号简介
    【.Net基础02】XML序列化问题
    【.net 基础01】ReferenceEquals,Equals,==的区别
    【Visual Studio】利用预编译命令发布不同的版本
    【Windows Phone 8】五角星评价控件
  • 原文地址:https://www.cnblogs.com/ants/p/4961487.html
Copyright © 2011-2022 走看看