最近用C#写服务器时,出现了死锁现象,刚开始但不知道哪里出了问题。
很难排查,直到我写了另一个线程去输出一个数据时(代码中有lock()),然后发现一执行排查线程就阻塞,发现就一直阻塞死锁在:lock()的地方,然后着手学习了一下:C#的死锁、线程同步。
死锁栗子
我们知道一种比较明显的死锁写法:
// 情况1:多个lock对象,互相嵌套lock,很容死锁
// 测试多个lock不同的对象,很容易死锁(这里的代码在CSDN线编辑器写的,可能会有问题)
// author : Jave.Lin
// date : 2018-04-21
public class TestDeadLock {
private object A = new object();
private object B = new object();
public void RunTask1() {
new Task(()=>{
// 如果RunTask1会先lock(A)的话
// 那么RunTask2中执行lock(A)时会阻塞,需要等RunTask1的lock(A)内的代码执行完
lock(A) {
// 这里先睡眠1000毫秒,保证RunTask2的lock(B)先执行
Thread.Sleep(1000);
lock(B) {
} // end lock(B)
} // end lock(A)
}).Start();
} // end RunTask1 func
public void RunTask2() {
new Task(()=>{
lock(B) {
// RunTask1会先lock(A)的话
// 所以这里lock(A)时会阻塞,需要等这里的RunTask1的lock(A)内的代码执行完
// 而RunTask1的lock(A)内的代码有lock(B),也在等RunTask2的lock(B)
// 所以双方互等,无限等而死锁
// 这里先睡眠1000毫秒,保证RunTask1的lock(A)先执行
Thread.Sleep(1000);
lock(A) {
} // end lock(A)
} // end lock(B)
}).Start();
} // end RunTask2 func
} // end class Definition
/*
简单理解为:
大家互相拿到自己的锁后,这时还想拿对方的锁,就会死锁
*/
再来看另一个死锁的写法
我们总结了上面一个例子的死锁情况,然后我们尽力避免不必要的lock对象,但还死锁了
情况2:每个类下,只有一个lock对象,一样死锁
总结为:不论一类下有多少个:lock对象
如果调用写法含类似结构:
Thread1.Run(()=>{lock(前)、lock(后)})
Thread2.Run(()=>{lock(后)、lock(前)})
那么都挺大几率会死锁,而且,嵌套深度不限,只要有其中一个嵌套深度下有互为嵌套lock关系的,都有可能死锁
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestDeadLock
{
class A
{
private object AL = new object();
public void ACall(Action a)
{
lock (AL) a();
}
}
class B
{
private object BL = new object();
public void BCall(Action a)
{
lock (BL) a();
}
}
/// <summary>
/// 测试:一个类定义下,只有一个private object locker,一样有死锁的可能
/// author : Jave.Lin
/// date : 2018-04-21
/// </summary>
class Program
{
static object locker = new object();
static void Main(string[] args)
{
var a = new A();
var b = new B();
new Task(() =>
{
Console.WriteLine("Task1.Started...");
a.ACall(() =>
{
Console.WriteLine("Task1.a.ACall Entered...");
Thread.Sleep(1000);
b.BCall(() =>
{
Console.WriteLine("Task1.b.BCall Entered...");
Thread.Sleep(1000);
Console.WriteLine("Task1.b.BCall Exit...");
});
Console.WriteLine("Task1.a.ACall Exit...");
});
Console.WriteLine("Task1.Exiting...");
}).Start();
new Task(() =>
{
Console.WriteLine("Task2.Started...");
b.BCall(() =>
{
Console.WriteLine("Task2.b.BCall Entered...");
a.ACall(() =>
{
Console.WriteLine("Task2.a.ACall Entered...");
Thread.Sleep(1000);
Console.WriteLine("Task2.a.ACall Exit...");
});
Thread.Sleep(1000);
Console.WriteLine("Task2.b.BCall Exit...");
});
Console.WriteLine("Task2.Exiting...");
}).Start();
lock (locker) Monitor.Wait(locker);
}
}
}
运行截图:
(死锁后程序不会往下执行,就一直阻塞这,卡在这输出,大家可以copy代码到Console项目运行即可看到上图的效果)
如何避免
- 什么是死锁,如何避免死锁? - 1
- 三种避免死锁的思路 - 1