在服务器应用程序上,我们具有以下内容:
一个称为JobManager的类,是一个单例。
另一个类(调度程序)将继续检查是否该将任何类型的作业添加到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进行的。
我认为,触发Indy Thread上下文上运行将消息发送到JobManager的服务器应用程序方法的RPC调用。

调度程序有其自己的线程正在运行,并且直接调用JobManager方法。这种情况容易陷入僵局吗?
有人可以帮我理解为什么这里发生僵局吗?

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

更多信息

我想补充一点(这可能很愚蠢,但是无论如何...)在DoSomething内部还有另一个
  CS.Acquire;
  try
    Do other stuff...
  finally
    CS.Release;
  end;

此内部CS.Release对外部CS.Acquire有什么作用?如果是这样,这可能是调度程序进入关键部分的时刻,所有的锁定和解锁都变得一团糟。

最佳答案

系统信息不足,无法确切地告诉您JobManager和Scheduler是否引起了死锁,但是如果它们都调用相同的NewJobItem方法,那么这应该不是问题,因为它们都将获取以相同的顺序锁定。

对于您的问题,您的NewJobItem CS.acquire和DoSomething CS.acquire是否彼此交互:这取决于。如果两种方法中使用的锁对象都不相同,则这两个调用不应是独立的。如果是同一对象,则取决于锁的类型。如果您的锁是可重入的锁(例如,它们允许从同一线程多次调用获取,并计算已获取和释放了多少次),那么这应该不是问题。另一方面,如果您有不支持重新输入的简单锁对象,则DoSomething CS.release可以释放该线程的锁,然后CallAMethodWithAnotherCriticalSessionInternally将在没有保护在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。

这是一个僵局。

有几种常见的解决方案:
  • 在您的所有代码中仅使用一个共享的全局锁。这样,不可能有两个线程等待两个锁。这使您的代码非常等待锁可用。
  • 仅允许您的代码一次持有一个锁。这通常很难控制,因为您可能不知道或无法控制方法调用的行为。
  • 仅允许您的代码同时获取所有锁,并同时释放所有锁,并且在已经获取锁的情况下禁止获取新锁。
  • 确保以相同的全局顺序获取所有锁。这是更常见的技术。

  • 对于解决方案4,您需要进行仔细的编程,并始终确保以相同的顺序获取锁/关键部分。为了帮助调试,您可以对系统中的所有锁进行全局排序(例如,每个锁仅使用一个唯一的整数),然后在尝试获取排名低于该锁的锁时抛出错误。当前线程已经被获取(例如,如果new_lock.id
    如果您不能使用全局调试工具来帮助您找到乱码获取的锁,我建议您在代码中找到所有获取锁的地方,并仅显示带有以下内容的调试消息:当前时间,调用获取/释放的方法,线程ID和正在获取的锁ID。对所有发布调用也执行相同的操作。然后运行系统,直到获得死锁,并在日志文件中找到哪个线程以哪个顺序获取了哪些锁。然后确定哪个线程正在以错误的顺序访问它的锁并进行更改。

    10-01 17:29