使用lock(this)来说明死锁的示例代码
我读过几篇文章和post,说lock(this)
, lock(typeof(MyType))
, lock("a string")
都是不好的做法,因为另一个线程可以锁定同一个键并导致死锁。 为了理解这个问题,我试图创建一些示例代码来说明死锁,但一直无法解决这个问题。
有人可以写一个简洁的代码来说明这个经典问题吗? 请保持简短,我只能在较小的块中消化代码。
编辑:我认为lassevk总结得很好; 真正的问题是你失去了对锁的控制。 一旦发生这种情况,您无法控制锁被调用的顺序,并且您允许潜在的死锁情况。
lock(this)
, lock(typeof(MyType))
等都是你选择了一个无法控制的锁的情况。
只有拥有多个锁时才会出现死锁。 您需要一种情况,即两个线程都拥有另一个需要的资源(这意味着必须至少有两个资源,并且两个线程必须尝试以不同的顺序获取它们)
这是一个简单的例子:
// thread 1 lock(typeof(int)) { Thread.Sleep(1000); lock(typeof(float)) { Console.WriteLine("Thread 1 got both locks"); } } // thread 2 lock(typeof(float)) { Thread.Sleep(1000); lock(typeof(int)) { Console.WriteLine("Thread 2 got both locks"); } }
假设两个线程都在彼此的一秒内启动,那么在任何人进入内部锁之前,他们都有时间抓住第一个锁。 如果没有Sleep()调用,其中一个线程很可能有时间在另一个线程开始之前获取并释放两个锁。
我们的想法是,你永远不应该锁定你无法控制谁有权访问的东西。
类型对象是每个.net代码段都可见的单例,您无法控制从外部锁定“this”对象的人。
同样的事情是字符串:因为字符串是不可变的,所以框架只保留一个“硬编码”字符串的实例并将它们放在一个池中(字符串被称为实习),如果你在代码中写两次字符串“你好,“你将永远得到同样的沮丧。
考虑以下示例:您在超级私有调用中只编写了Thread1,而在后台线程中使用的某些库调用了Thread2 …
void Thread1() { lock (typeof(int)) { Thread.Sleep(1000); lock (typeof(long)) // do something } } void Thread2() { lock (typeof(long)) { Thread.Sleep(1000); lock (typeof(int)) // do something } }
喏,给你。
请注意,死锁的常见示例是当您获得多个锁时,两个或多个线程最终等待彼此。
例如,两个线程像这样锁定:
Thread 1 Thread 2 Lock "A" Lock "B" Lock "B" Lock "A" <-- both threads will stop dead here waiting for the lock to be come available.
但是,在这个例子中我没有理会,我只是让一个线程无限期地锁定。 你真的不想放松对你的锁的控制,所以尽管这是一个人为的例子,后台线程可以像这样完全阻塞主线程的事实是不好的。
using System; using System.Threading; namespace ConsoleApplication7 { public class Program { public static void Main(string[] args) { LockableClass lockable = new LockableClass(); new Thread(new ParameterizedThreadStart(BackgroundMethod)).Start(lockable); Thread.Sleep(500); Console.Out.WriteLine("calling Reset"); lockable.Reset(); } private static void BackgroundMethod(Object lockable) { lock (lockable) { Console.Out.WriteLine("background thread got lock now"); Thread.Sleep(Timeout.Infinite); } } } public class LockableClass { public Int32 Value1 { get; set; } public Int32 Value2 { get; set; } public void Reset() { Console.Out.WriteLine("attempting to lock on object"); lock (this) { Console.Out.WriteLine("main thread got lock now"); Value1 = 0; Value2 = 0; } } } }
这是非常标准的坏事。 抓住锁无序,然后用锁睡觉。 两件坏事要做。 🙂
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; namespace DeadLock { public class Program { static void Main(string[] args) { var ddt = new DontDoThat(); ddt.Go(); } } public class DontDoThat { private int _badSharedState = 0; private readonly object _lock1 = new object(); private readonly object _lock2 = new object(); public void Go() { new Thread(BadGuy1).Start(); new Thread(BadGuy2).Start(); Console.WriteLine("Leaving Go!"); } public void BadGuy1() { lock (_lock1) { Thread.Sleep(100); // yeild with the lock is bad lock (_lock2) { _badSharedState++; Console.Write("From Bad Guy #1: {0})", _badSharedState ); } } } public void BadGuy2() { lock (_lock2) { lock (_lock1) { _badSharedState++; Console.Write("From Bad Guy #2: {0})", _badSharedState); } } } } }
class Character { public Character Other; public string Name; private object locker = new object(); public Character(string name) { Name = name; } public void Go() { lock (locker) { Thread.Sleep(1000); Console.WriteLine("go in {0}", Name); Other.Go(); } } } class Program { static void Main(string[] args) { Character a = new Character("A"); Character b = new Character("B"); a.Other = b; b.Other = a; new Thread(a.Go).Start(); b.Go(); Console.ReadLine(); } }
问题是锁(“一个字符串”)锁定单例。 这意味着使用相同锁的其他对象可能是无限等待。
例如:
using System; using System.Threading; namespace ThreadLock { class Program { static void Main(string[] args) { lock ("my lock") { ManualResetEvent evt = new ManualResetEvent(false); WorkerObject worker = new WorkerObject(evt); Thread t = new Thread(new ThreadStart(worker.Work)); t.Start(); evt.WaitOne(); } } } class WorkerObject { private ManualResetEvent _evt; public WorkerObject(ManualResetEvent evt) { _evt = evt; } public void Work() { lock ("my lock") { Console.WriteLine("worked."); _evt.Set(); } } } }
在这种情况下,调用代码会在字符串上创建一个锁,然后生成一个worker对象。 Work()中的worker对象锁定在同一个字符串上,该字符串是C#中的单例。 它最终处于死锁状态,因为调用者拥有锁并正在等待永远不会发出的信号。