C# 使用lock(this)演示死锁的示例代码
我读过几篇文章和帖子说,C# 使用lock(this)演示死锁的示例代码,c#,.net,multithreading,deadlock,C#,.net,Multithreading,Deadlock,我读过几篇文章和帖子说,lock(this),lock(typeof(MyType)),lock(“字符串”)都是错误的做法,因为另一个线程可能会锁定同一个密钥并导致死锁。为了理解这个问题,我试图创建一些示例代码来说明死锁,但一直无法理解这个问题 有人能写一段简洁的代码来说明这个经典问题吗?请保持简短,我只能将代码分解成小块 编辑: 我认为拉塞夫克总结得很好;真正的问题是你失去了对锁的控制。一旦发生这种情况,您就无法控制锁的调用顺序,并且您允许出现潜在的死锁情况 lock(this),lock(
lock(this)
,lock(typeof(MyType))
,lock(“字符串”)
都是错误的做法,因为另一个线程可能会锁定同一个密钥并导致死锁。为了理解这个问题,我试图创建一些示例代码来说明死锁,但一直无法理解这个问题
有人能写一段简洁的代码来说明这个经典问题吗?请保持简短,我只能将代码分解成小块
编辑:
我认为拉塞夫克总结得很好;真正的问题是你失去了对锁的控制。一旦发生这种情况,您就无法控制锁的调用顺序,并且您允许出现潜在的死锁情况
lock(this)
,lock(typeof(MyType))
等都是您选择了一个无法控制的锁的情况。当然,给您
请注意,死锁的常见示例是当您获取多个锁时,两个或多个线程最终会互相等待
例如,两个线程的锁定方式如下:
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.
只有当您有多个锁时,才会发生死锁。您需要这样一种情况:两个线程都持有另一个线程需要的资源(这意味着必须至少有两个资源,并且两个线程必须尝试以不同的顺序获取它们) 举个简单的例子:
// 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()调用,其中一个线程很可能有时间在另一个线程启动之前获取并释放这两个锁。问题在于锁(“字符串”)锁定的是单例。这意味着使用相同锁的其他对象可能是无限等待
例如:
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#中是一个单例。它以死锁告终,因为调用者拥有锁并且正在等待一个永远不会出现的信号。这是非常糟糕的。把锁抓得乱七八糟,然后拿着锁睡觉。做两件坏事。:)
这个想法是,你永远不应该锁定你无法控制谁有权访问的东西 类型对象是每个.net代码段都可见的单例对象,您无法从外部控制谁锁定“this”对象 字符串也是一样:因为字符串是不可变的,所以框架只保留一个“硬编码”字符串的实例,并将它们放在一个池中(该字符串被称为interned),如果您在代码中编写两次字符串“hello”,您将始终得到相同的对象 考虑以下示例:您在超级私有调用中只编写了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
}
}
这不会死锁,因为同一个线程正在尝试执行两个locsk,所以不会发生争用。如果ob.Work()在另一个线程上执行,它仍然不会死锁,因为第一个锁迟早会被释放。两个线程都必须被阻塞,等待对方死锁。对其他答案的评论正确地表明,我在多锁场景中描述的是“死锁”,但这里的主要问题是,您已经失去了对锁的控制,而不是它是否是真正的死锁。您永远不想失去对锁的控制。无限期锁定的线程是否被视为死锁情况?thread.Sleep(Timeout.Infinite);是不是死锁((()())())))啊哈,在你发布它的时候,我写的是完全相同的示例:)但我选择了一个很长很好的示例:创建死锁的一个关键因素是以不同的顺序锁定两个资源。是的,这就是构建分层锁等技术的原理,也是它们非常有助于避免死锁的原因。+1简而言之:
记住,死锁只有在拥有多个锁的情况下才会发生。您需要这样一种情况,即两个线程都持有另一个线程需要的资源(这意味着必须至少有两个资源,并且两个线程必须尝试以不同的顺序获取它们)
。这是我一直在寻找的基本想法。现在,分析任何多线程代码都很容易。我是否遗漏了什么?首先,完全有可能只使用一个锁就获得死锁。其次,虽然上面的示例清楚地说明了死锁的可能性,但它与锁定this
或typeof(x)
无关。如果锁定私有对象,死锁同样可能发生,因此虽然这是一个很好的建议,但我不确定它是否真的回答了这个问题。我错了吗?
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);
}
}
}
}
}
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
}
}
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();
}
}