死锁Delphi解释/解决方案

时间:2011-05-11 01:45:12

标签: multithreading delphi deadlock

在服务器应用程序中,我们有以下内容: 一个名为JobManager的类,它是一个单例。 另一个类Scheduler,它一直在检查是否是时候向JobManager添加任何类型的作业。

当需要这样做时,调度程序会执行以下操作:

TJobManager.Singleton.NewJobItem(parameterlist goes here...);

同时,在客户端应用程序上,用户执行生成对服务器的调用的操作。在内部,服务器向自己发送消息,其中一个侦听该消息的类是JobManager。 JobManager处理消息,并且知道是时候将新作业添加到列表中,调用它自己的方法:

NewJobItem(parameter list...);

在NewJobItem方法中,我有类似的东西:

  CS.Acquire;
  try
    DoSomething;
    CallAMethodWithAnotherCriticalSessionInternally;
  finally
    CS.Release;
  end;

此时系统出现死锁(CS.Acquire)。 客户端和服务器应用程序之间的通信是通过Indy 10完成的。 我认为,激活向JobManager发送消息的服务器应用程序方法的RPC调用正在Indy Thread的上下文中运行。

Scheduler有自己的线程在运行,它直接调用JobManager方法。这种情况容易出现死锁吗? 有人能帮助我理解为什么会发生僵局吗?

我们知道,有时候,当客户端做了一个特定的动作,导致系统锁定时,我终于可以找到这一点,同一个类的关键部分从不同的点到达两次(调度程序和JobManager的消息处理程序方法。

更多信息

我想补充一点(这可能很傻,但无论如何......)在DoSomething里面还有另一个

  CS.Acquire;
  try
    Do other stuff...
  finally
    CS.Release; 
  end;

这个内部CS.Release对外部CS.Acquire做了什么?如果是这样,这可能是调度程序进入关键部分的点,并且所有锁定和解锁都变得一团糟。

1 个答案:

答案 0 :(得分:2)

关于你的系统没有足够的信息可以告诉你你的JobManager和Scheduler是否导致死锁,但如果他们都调用相同的NewJobItem方法,那么这应该不是问题,因为他们会两者都以相同的顺序获得锁。

如果你的问题是你的NewJobItem CS.acquire和DoSomething CS.acquire相互影响:它取决于你。如果两种方法中使用的锁定对象不同,则两个调用不应该是独立的。如果它是同一个对象,那么它取决于锁的类型。如果锁是重入锁(例如,它们允许从同一个线程多次调用获取并计算它们被获取和释放的次数)那么这应该不是问题。另一方面,如果您有简单的锁定对象不支持重新输入,那么DoSomething CS.release可以释放您对该线程的锁定,然后CallAMethodWithAnotherCriticalSessionInternally将在没有保护所获取的CS锁定的情况下运行NewJobItem。

当有两个或多个线程在运行且每个线程正在等待另一个线程完成它的当前作业,然后它才能继续运行时,就会发生死锁。

例如:

Thread 1 executes:

lock_a.acquire()
lock_b.acquire()
lock_b.release()
lock_a.release()


Thread 2 executes:

lock_b.acquire()
lock_a.acquire()
lock_a.release()
lock_b.release()

请注意,线程2中的锁是以与线程1相反的顺序获取的。现在,如果线程1获取了lock_a,然后被中断,线程2现在运行并获取lock_b,然后开始等待lock_a在它之前可用可以继续然后线程1继续运行,它接下来要做的就是尝试获取lock_b,但它已被线程2占用,所以它等待。最后,我们处于线程1正在等待线程2释放lock_b并且线程2正在等待线程1释放lock_a的情况。

这是一个僵局。

有几种常见的解决方案:

  1. 仅在所有代码中使用一个共享全局锁。这样就不可能让两个线程等待两个锁。这使得您的代码等待很多,以使锁可用。
  2. 只允许您的代码一次保留一个锁。这通常太难控制,因为您可能不知道或控制方法调用的行为。
  3. 只允许您的代码同时获取多个锁,并同时释放它们,并且在您已经获得锁定时禁止获取新锁。
  4. 确保以相同的全局顺序获取所有锁。这是一种更常见的技术。
  5. 使用解决方案4.您需要小心编程并始终确保以相同的顺序获取锁定/关键部分。为了帮助调试,您可以在系统中的所有锁上放置一个全局顺序(例如,每个锁只有一个唯一的整数),然后如果您尝试获取一个锁的排名较低的锁,则会抛出错误当前线程已经获取(例如,如果new_lock.id< lock_already_acquired.id然后抛出异常)

    如果你不能放入一个全局调试辅助工具来帮助找到不按顺序获取的锁,我建议你找到代码中你获得任何锁的所有位置并打印调试具有当前时间的消息,调用获取/释放的方法,线程ID和正在获取的锁定ID。所有发布调用也都做同样的事情。然后运行您的系统,直到遇到死锁并在您的日志文件中找到哪些线程已获取哪些线程以及以哪种顺序获取。然后决定哪个线程以错误的顺序访问它的锁并更改它。