C#多线程学习笔记十四

多线程中常遇到的问题和调试


使用WIndbg来查看线程和堆栈,利用ILSpy反编译代码找到问题可能在的地方。

CPU占用率过高问题

例如下面这段程序,假如while循环中有一个复杂的程序导致死循环,久而久之会导致CPU占用率过高。

static void Run()
{
    
    
    Task task = Task.Factory.StartNew(() =>
    {
    
    
        var i = true;

        //假设这里有一个复杂的程序导致死循环
        while (true)
        {
    
    
            i = !i;
        }
    });
}

如何用windbg去找到问题,调试dump文件?
演示步骤:

  1. 生成release x64版本
  2. 在“任务管理器”中生成“.DMP” 转储文件
  3. 用x64版本windbg打开该文件
  4. !runaway 查看当前托管线程已执行时间
    在这里插入图片描述
    3fd8线程运行时间很长,而其他线程运行时间很短,所以3fd8线程得到怀疑
  5. 切换到指定的线程,这里是3fd8线程,命令:~~[3fd8]s
  6. 查看当前线程的调用堆栈 命令:!clrstack
    在这里插入图片描述
    从调用堆栈上来看,当前线程在_24Solution.Program+c.b__1_0() 之后就没调用堆栈了,说明方法在此处停滞不前了。依此找到命名空间_24Solution的类Program里
  7. 用ILSpy打开文件找到下图位置发现了c.b__1_0()方法的while循环有问题
    在这里插入图片描述
    8.以5-7中的步骤,依此查看4中有问题的线程排查问题。

死锁问题

在Lock中调用lock,或者说lock中调用的方法也调用了lock会导致死锁问题。

        //死锁
        static object locker = new object();
        static void Run()
        {
    
    
            lock (locker)
            {
    
    
                var task = Task.Run(() =>
                {
    
    
                    Console.WriteLine("----------------Run-----------------");
                    Thread.Sleep(1000);
                    Run2();
                    Console.WriteLine("----------------End-----------------");
                });
                task.Wait();
            }
        }

        static void Run2()
        {
    
    
            lock (locker)
            {
    
    
                Console.WriteLine("----------------Run2-----------------");
            }
        }

上面这段代码运行会受到阻塞。那么怎么发现问题呢?
演示步骤:

  1. ~*e!clrstack 查看所有线程堆栈 ,命令输入后出现busy时.clr清除屏幕再次输出查看命令
    在这里插入图片描述
    在这里插入图片描述
    线程堆栈中:
    发现线程4de4调用System.Threading.Monitor.ObjWait在等待锁
    而线程0x47调用System.Threading.Monitor.ReliableEnter进入锁
    细看 线程4de4中Run方法和Main方法已经入栈,而线程474中Run2方法和_24Solution.Program+c.b__2_0()方法入栈

  2. !thread 查看当前的托管线程 发现474为工作线程
    在这里插入图片描述

  3. !syncblk 查看当前哪个线程持有锁
    在这里插入图片描述
    可以看出是线程4de4拥有同步锁,那么肯定有其他的线程在执行Monitor.Enter时锁住了导致运行不下去,也就是调用到堆栈顶部停下。
    <4>综合上面这些信息可以看到执行完_24Solution.Program+c.b__2_0()方法,再执行Run2方法后调用System.Threading.Monitor.ReliableEnter想进入锁,而锁已被线程4de4占用,从而Run2方法一直在等待,造成了死锁。找到Run2方法,并用ILSpy打开exe文件发现c.b__2_0()如下。并发现这段程序的来源与Run方法,结合Run2代码发现问题是Run中的lock还没释放,此时Run2申请锁资源造成死锁。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

内存爆满

        static StringBuilder sb = new StringBuilder();
        static void Main(string[] args)
        {
    
    
            //Run();
            for (int i = 0; i < 10000000; i++)
            {
    
    
                sb.Append("hello word");
            }

            Console.WriteLine("finish");
            Console.Read();
        }

场景:内存占用205MB。
在这里插入图片描述
演示步骤:
1.!dumpheap -stat 查看clr的托管堆中各个类型的占用情况
在这里插入图片描述
char[]数组有12518个占用200153452B约190MB
2.!dumpheap /d -mt 79a32c60(数字为具体MT) 获得方法表发现很多16012长度的char
在这里插入图片描述
3.!dumpobj /d 10bb7900(数字为怀疑Address) 获得内容,发现一串hello world
在这里插入图片描述
4.!gcroot 10bb7900 查看当前地址的根 发现均为StringBuilder
在这里插入图片描述
最终集合StringBuilder和“hello word”去代码中寻找问题。
推荐【.net高级调试】:讲述怎么使用windbg

猜你喜欢

转载自blog.csdn.net/Z960515/article/details/113444336