2011-05-11 92 views
6

在服务器应用程序中,我们有以下内容: 一个称为JobManager的类,它是一个单例。 另一个类Scheduler会不断检查是否需要将任何作业添加到JobManager。死锁Delphi解释/解决方案

当是时候这样做,调度这样做:

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线程的上下文中运行。

调度程序有其自己的线程正在运行,并直接调用JobManager方法。这种情况容易发生死锁吗? 有人能帮我理解为什么这里发生了一个僵局吗?我们知道,有时,当客户端做了一个特定的动作,导致系统锁定,那么我终于可以找到这一点,同一班级的关键部分从不同的点到达两次调度程序和JobManager的消息处理程序方法)。

一些更多的信息

我想补充一点,(这可能是愚蠢的,但无论如何...)的DoSomething的里面还有另外一个

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

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

+0

关键部分的目的是为了保护不能在不同线程上同时执行的代码,所以如果在同一个实例上的关键部分达到两次(或更多)是好的,因为这意味着关键部分是在那里工作,它是为此而设计的!坏消息是当CS1因为获取线程正在等待获取CS2而没有释放,而CS2由于获取线程正在等待获取CS1(称为DeadLock)而未释放。从你说的话,我不确定你正在经历一个僵局......你为什么确定? – jachguate 2011-05-11 02:30:54

+0

调试时,按照消息处理程序方法,我刚刚进入临界区,并且CS.Acquire行处的断点再次到达调度程序 - 再次触发F8,系统停止。 – ronaldosantana 2011-05-11 03:01:02

+5

您也可以在Windows消息处理循环中发生死锁。当ThreadA在CS1上有一个锁定,然后进行需要消息循环循环的调用时;并且线程B实际上在等待获取CS1时停止,中断或无限期地延迟消息循环... – 2011-05-11 06:11:30

回答

2

没有足够的有关系统的信息能够明确告诉您JobManager和Scheduler是否导致死锁,但是如果它们都调用相同的NewJobItem方法,那么这应该不是问题,因为它们都将以相同的顺序获取锁。

对于你的问题,如果你的NewJobItem CS.acquire和DoSomething CS.acquire相互交互:这取决于。如果两种方法中使用的锁对象不同,则两个调用不应该是独立的。如果它是同一个对象,则它取决于锁的类型。如果您锁定的是重入锁(例如,它们允许从同一个线程多次调用获取并计算它们被获取和释放的次数),那么这应该不成问题。另一方面,如果你有简单的不支持重新输入的锁定对象,那么DoSomething CS.release可以释放你对该线程的锁定,然后CallAMethodWithAnotherCriticalSessionInternal将在没有保护获取的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. 确保所有锁都是以相同的全局顺序获取的。这是一种更常见的技术。

有了解决方案4.您需要小心编程,并始终确保以相同的顺序获取锁定/关键部分。为了帮助调试,您可以在系统中的所有锁上放置一个全局顺序(例如,每个锁只有一个唯一的整数),然后在尝试获取排名较低的锁的时候抛出一个错误,当前线程已经获得(例如,如果new_lock.id < lock_already_acquired.id然后抛出异常)

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