多线程中常遇到的问题和调试
使用WIndbg来查看线程和堆栈,利用ILSpy反编译代码找到问题可能在的地方。
CPU占用率过高问题
例如下面这段程序,假如while循环中有一个复杂的程序导致死循环,久而久之会导致CPU占用率过高。
static void Run()
{
Task task = Task.Factory.StartNew(() =>
{
var i = true;
//假设这里有一个复杂的程序导致死循环
while (true)
{
i = !i;
}
});
}
如何用windbg去找到问题,调试dump文件?
演示步骤:
- 生成release x64版本
- 在“任务管理器”中生成“.DMP” 转储文件
- 用x64版本windbg打开该文件
- !runaway 查看当前托管线程已执行时间
3fd8线程运行时间很长,而其他线程运行时间很短,所以3fd8线程得到怀疑 - 切换到指定的线程,这里是3fd8线程,命令:~~[3fd8]s
- 查看当前线程的调用堆栈 命令:!clrstack
从调用堆栈上来看,当前线程在_24Solution.Program+c.b__1_0() 之后就没调用堆栈了,说明方法在此处停滞不前了。依此找到命名空间_24Solution的类Program里 - 用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-----------------");
}
}
上面这段代码运行会受到阻塞。那么怎么发现问题呢?
演示步骤:
-
~*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()方法入栈 -
!thread 查看当前的托管线程 发现474为工作线程
-
!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