C#线程问题之死锁

过多的锁定也会有麻烦。在死锁中,至少有两个线程被挂起,并等待对方解除锁定。由于两个线程都在等待对方,就出现了死锁,线程将无限等待下去。.

为了说明死锁,下面实例化 StateObject 类型的两个对象,并把它们传递给SampleTask 类的构造函数。创建两个任务,其中一个任务运行 Deadlock1() 方法,另一个任务运行 Deadlock2() 方法:

var statel = new StateObject(); 
var state2 = new StateObject();
new Task(new SampleTask(statel, state2).Deadlock1).Start(); 
new Task(new SampleTask(statel, state2).Deadlock2).Start();

Deadlock1() 和 Deadlock2() 方法现在改变两个对象 s1和 s2 的状态,所以生成了两个锁。Deadlock1() 方法先锁定 sl,接着锁定 s2。Deadlock2() 方法先锁定 s2,再锁定 sl。现在,有可能Deadlock1() 方法中 sl 的锁定会被解除。接着,出现一次线程切换,Deadlock2() 方法开始运行,并锁定 s2。第二个线程现在等待sl 锁定的解除。因为它需要等待,所以线程调度器再次调度第一个线程,但第一个线程在等待 s2 锁定的解除。这两个线程现在都在等待,只要锁定块没有结束,就不会解除锁定。这是一个典型的死锁。

public class SampleTask
{
  public SampleTask(StateObject sl, StateObject s2)
  {
    _sl = sl; 
    _s2 = s2;
  }
  private StateObject _sl;
  private StateObject _s2; 
  
  public void Deadlock1() 
  {
    int i = 0;
    while (true) 
    {
      lock (_s1) 
      {
        lock (_s2)
        {
          _s1.ChangeState(i); 
          _s2.ChangeState(i++);
          Console.WriteLine($"still running, {i}");
         }
       }
     }
  }
  public void Deadlock2()
  {
    int i = 0;
    while (ture)
    {
      lock (_s2) 
      {
        lock (_s1)
        {
          _s1.ChangeState(i); 
          _s2.ChangeState(i++);
          Console.WriteLine($"still running, {i}");
        }
      }
    }
  }
}

结果是,程序运行了许多次循环,不久就没有响应了。“仍在运行” 的消息仅写入控制台中几次。同样,死锁问题的发生频率也取决于系统配置,每次运行的结果都不同。

死锁问题并不总是像这样那么明显。一个线程锁定了 s1,接着锁定 s2;另一个线程锁定了 s2,接着锁定 s1。在本例中只需要改变锁定顺序,这两个线程就会以相同的顺序进行锁定。但是,在较大的应用程序中,锁定可能隐藏在方法的深处。为了避免这个问题,可以在应用程序的体系架构中,从一开始就设计好锁定顺序,也可以为锁定定义超时时间。