《C#多线程编程实战》1.11 Monitor.TryEnter()避免死锁
这章的内容是真的有意思
特别是代码。
先贴上代码:
class Program { static void Main(string[] args) { object lock1 = new object(); object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock(lock2) { Thread.Sleep(1000); Console.WriteLine("Monitor.TryEnter allow not to get stuck,returning false after a specified timer is elapsed"); if(Monitor.TryEnter(lock1,5000)) { Console.WriteLine("Acquired a protected resource succesfully"); } else { Console.WriteLine("Timerout cquiring a resource"); } } new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2) { Console.WriteLine("This will be deadlock"); Thread.Sleep(1000); lock (lock1) { Console.WriteLine("Acquired a protected resource succesfully"); } } Console.ReadKey(); } static void LockTooMuch(object Lock1,object Lock2) { lock(Lock1) { Thread.Sleep(1000); lock (Lock2) { } } } }
说实话,这章代码,我一开始没看明白。真的。
有些时候真的就是难者不会会者不难。
咱慢慢来来说。
咱先从这一部分开始:
1 static void LockTooMuch(object Lock1,object Lock2) 2 { 3 lock(Lock1) 4 { 5 Thread.Sleep(1000); 6 7 lock (Lock2) 8 { 9 10 } 11 12 } 13 }
这一部分的代码很好理解:
传入两个引用类型的object参数。
然后是代码主要部分先是
锁死lock1
然后再lock1里面锁死lock2.
这个部分的锁死的代码,也就意味着解锁的顺序,lock1等待lock2执行完毕才会解锁lock1.
本章说避免锁死。那么什么是锁死?
那么我们来原书的代码:
new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2) { Console.WriteLine("This will be deadlock"); Thread.Sleep(1000); lock (lock1) { Console.WriteLine("Acquired a protected resource succesfully"); } }
这一部分的代码加上静态的LockTooMatch的方法运行就会锁死。
我们按照这个思路清理一下代码:
static void Main(string[] args) { object lock1 = new object(); object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2) { Console.WriteLine("This will be deadlock"); Thread.Sleep(1000); lock (lock1) { Console.WriteLine("Acquired a protected resource succesfully"); } } Console.ReadKey(); } static void LockTooMuch(object Lock1,object Lock2) { lock(Lock1) { Thread.Sleep(0000); lock (Lock2) { } } }
see
永远锁死
为什么只会执行到这里呢?
Console.WriteLine("This will be deadlock");
一步一步的
首先程序新开线程运行LockTooMuch,然后再主线程锁LOCK2输出内容,线程等待1S之后再锁LOCK1 在输出语句。
1 lock (lock2) 2 { 3 Console.WriteLine("This will be deadlock"); 4 Thread.Sleep(1000); 5 lock (lock1) 6 { 7 Console.WriteLine("Acquired a protected resource succesfully"); 8 } 9 10 }
按照我们之前说的东西,L2必须等待L1结束才会释放。
这是主线程的部分。
下面是方法的部分
static void LockTooMuch(object Lock1,object Lock2) { lock(Lock1) { Thread.Sleep(1000); lock (Lock2) { } } }
同样也是L1等待L2的结束才会是否。
主线程和新开线程是同时的。
也就是会有竞争。
那么执行的顺序是什么呢?
很简单
主线程新开线程
主线锁LOCK2
新开线程执行锁LOCK1
主线程输出
新开线程等待1s
主线程等待1s
新开线程锁LOCK2但是失败
主线程锁LOCK1但是失败
程序停止继续向下运行
为什么主线程,新线程锁LOCK为什么会失败呢?
新开线程锁lock2会失败是因为主线程在用lock2而且还没有用完。
主线程锁lock1会失败是应为新开线程做用lock1还么有用完。
所以会锁死。
ok 我们知道锁死的原因,下面我们通过Monitor.TryEnter来避免锁死。
Monitor.TryEnter是作用什么呢?
TryEnter有三个重载方法。
常用的是带有等待时间的。
作用就是在规定的时间去尝试解锁资源,或者获取资源。如果获取成功就返回true,不成功就返回false。这个代码总会返回结果。超时也会返回不过是false,也就说锁死了也会有办法来处理,不会尴尬的停在那里了
我们看看代码:
new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock(lock2) { Thread.Sleep(1000); Console.WriteLine("Monitor.TryEnter allow not to get stuck,returning false after a specified timer is elapsed"); if(Monitor.TryEnter(lock1,5000)) { Console.WriteLine("Acquired a protected resource succesfully"); } else { Console.WriteLine("Timerout cquiring a resource"); } }
ok 清理下会锁死的代码,来运行代码
执行了else内的代码,说明尝试解锁lock1失败了,和上面那个会死锁的代码一样的结果,但是这个却可以继续持续其他代码,不会卡在死锁的部分。
我们来看看执行的过程【大家可以在VS中 使用断点,逐语句等方式来观察线程的运行顺序】
主线程新开线程
主线锁LOCK2
新开线程执行锁LOCK1
主线程等待1s
新开线程等待1s
主线程输出语句
新开线程锁LOCK2但是失败
主线程尝试解锁LOCK1,5s内。(成功返回true 失败返回fasle 超时返回false)但是失败
主线程输出else内容
程序结束
执行的顺序和死锁的顺序差不多,但是结果却不一样的因为Monitor.TryEnter避免的死锁的尴尬地步,程序多线程中出现死锁,使用monitor.tryenter会很好的避免死锁。