c#线程死锁
起因
做实施的同事,最近一直想转开发.在WinForm和Web之间徘徊.所以就有了话题.WinForm会经常遇到多线程,当然也可以是异步处理.
Web这里主要是(Asp.Net WebForm/Asp.Net MVC或者Asp.Net Core)天生是多线程的,很少去开启多线程,执行任务.
多线程使任务可以并行运行.和正常顺序执行是不一样,在使用的时候有很多注意事项.拿典型的说: 线程死锁. 在其他可以使用多线程语言中,线程死锁都是可能发生.
从单线程说起,代码示例
class Program
{
static int num = 0;
static void Main(string[] args)
{
TestNum();
Console.WriteLine($"num={num}");
Console.ReadKey();
}
public static void TestNum()
{
for (int i = 0; i < 10000000; i++)
{
num += 1;
}
}
}
上面的代码,可以很好的运行.计算得到的num,代码没有进行注释.
多线程示例
class Program
{
static int num = 0;
static void Main(string[] args)
{
Thread t1 = new Thread(TestNum);
t1.Start(); //线程1,开始调用TestNum
Thread t2 = new Thread(TestNum);
t2.Start(); //线程2,开始调用TestNum
t1.Join(); //等待线程1执行结束
t2.Join(); //等待线程2执行结束
Console.WriteLine($"num={num}"); //输出两个线程执行完的结果
Console.ReadKey();
}
public static void TestNum()
{
for (int i = 0; i < 10000000; i++)
{
num += 1;
}
}
}
执行结果:
为什么值不是我们预期的值呢?先看看下面两张图
怎么使用锁?
锁在现实生活中随处可见,比如说在自行车上锁,如果不打开锁,我们就无法骑行.在程序中,我们要锁住某个变量,让其他线程展示无法访问.只能开了锁,只能开了锁才能继续访问.class Program
{
static int num = 0;
static object obj1 = new object();
static void Main(string[] args)
{
Thread t1 = new Thread(TestNum);
t1.Start(); //线程1,开始调用TestNum
Thread t2 = new Thread(TestNum);
t2.Start(); //线程2,开始调用TestNum
t1.Join(); //等待线程1执行结束
t2.Join(); //等待线程2执行结束
Console.WriteLine($"num={num}"); //输出两个线程执行完的结果
Console.ReadKey();
}
public static void TestNum()
{
//lock 在c#中是关键字, 简单可以理解现实中的锁, 在一个线程中锁住obj1变量,其他线程要等待当前执行结束才能访问,
lock (obj1)
{
for (int i = 0; i < 10000000; i++)
{
num += 1;
}
}
}
}
在将上面的代码编译运行后,终于得到我们预期的值.
lock关键字,只是语法糖,是Monitor.Enter的简写
先看看这段代码.static object obj1 = new object();
static void Main()
{
lock(obj1)
{
Thread.Sleep(1000);
}
Console.WriteLine("ok");
Console.ReadKey();
}
主要通过ILSpy查看生成IL代码.
如果使用Monitor.Enter,代码应该这样写.
static object obj1 = new object();
static void Main()
{
try
{
Monitor.Enter(obj1);
Thread.Sleep(1000);
}
finally
{
Monitor.Exit(obj1);
}
Console.WriteLine("ok");
Console.ReadKey();
}
怎么发生死锁? 简单对代码进行升级
class Program
{
static int num = 0;
static object obj1 = new object();
static object obj2 = new object();
static void Main(string[] args)
{
Thread t1 = new Thread(TestDeadLock);
t1.Start(true); //线程1,开始调用TestNum
Thread t2 = new Thread(TestDeadLock);
t2.Start(false); //线程2,开始调用TestNum
t1.Join(); //等待线程1执行结束
t2.Join(); //等待线程2执行结束
Console.WriteLine($"num={num}"); //输出两个线程执行完的结果
Console.ReadKey();
}
/// <summary>
/// 测试线程死锁
/// </summary>
/// <param name="param"></param>
public static void TestDeadLock(object param)
{
bool flag = (bool)param;
if (flag)
{ //线程1执行
lock (obj1)
{
Console.WriteLine($"lock obj1 flag={flag}");
lock (obj2)
{
Console.WriteLine($"lock obj2 flag={flag}");
for (int i = 0; i < 10000000; i++)
{
num += 1;
}
}
}
}
else
{
//线程2执行
lock (obj2)
{
Console.WriteLine($"lock obj2 flag={flag}");
lock (obj1)
{
Console.WriteLine($"lock obj1 flag={flag}");
for (int i = 0; i < 10000000; i++)
{
num += 1;
}
}
}
}
}
}

线程死锁注意事项
- 尽量不共享全局变量.
- 避免线程多次锁.
- 在使用多线程时,设计要合理
秋风
2018-05-14