2011-05-11 18 views
6

En una aplicación de servidor tenemos lo siguiente: Una clase llamada JobManager que es un singleton. Otra clase, el Programador, que sigue comprobando si es el momento de agregar cualquier clase de trabajo al Administrador de trabajos.Explicación/solución de Deadlock Delphi

Cuando es el momento de hacerlo, el Programador de hacer algo como:

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

Al mismo tiempo, en la aplicación cliente, el usuario haga algo que genera una llamada al servidor. Internamente, el servidor se envía un mensaje a sí mismo, y una de las clases que escuchan ese mensaje es el JobManager. El Administrador de tareas maneja el mensaje, y sabe que es el momento de añadir una nueva tarea a la lista, llamando a su propio método:

NewJobItem(parameter list...); 

En el método NewJobItem, tengo algo como esto:

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

Ocurre que el sistema llega a un punto muerto en este punto (CS.Acquire). La comunicación entre el cliente y la aplicación del servidor se realiza a través de Indy 10. Creo que la llamada RPC que activa el método de aplicación del servidor que envía un mensaje al JobManager se ejecuta en el contexto de Indy Thread.

El Programador tiene su propio hilo ejecutándose y realiza una llamada directa al método JobManager. ¿Esta situación es propensa a estancamientos? ¿Alguien me puede ayudar a entender por qué está pasando un punto muerto aquí?

Sabíamos que, a veces, cuando el cliente realizaba una acción específica que causaba que el sistema se bloqueara, finalmente pude descubrir este punto, donde la sección crítica de la misma clase se alcanza dos veces, desde diferentes puntos (el Programador y el método del manejador de mensajes del JobManager).

Algunos más información

Quiero añadir que (esto puede ser tonto, pero de todos modos ...) dentro de la HacerAlgo hay otra

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

Este CS.Release interna está haciendo cualquier cosa al CS externo. ¿Adquirir? Si es así, este podría ser el punto donde el Programador está ingresando a la Sección Crítica, y todo el bloqueo y desbloqueo se convierte en un desastre.

+0

El propósito de una sección crítica es proteger el código que no se puede ejecutar al mismo tiempo en diferentes hilos, por lo que está bien si una sección crítica en la misma instancia se alcanza dos veces (o más) porque significa la sección crítica está haciendo su trabajo allí y ¡está diseñada para eso! Lo malo es cuando CS1 no se libera porque está adquiriendo subprocesos esperando para adquirir CS2, mientras que CS2 no se libera porque está adquiriendo subprocesos esperando adquirir CS1 (llamado un DeadLock). Por lo que dices, no estoy seguro de que estés en un punto muerto ... ¿por qué estás seguro? – jachguate

+0

Al depurar, siguiendo el método del controlador de mensajes, acabo de ingresar a la sección crítica, y mi punto de interrupción en la línea CS.Acquire, fue alcanzado nuevamente, proveniente del programador: presionando F8 nuevamente, el sistema se detuvo. – ronaldosantana

+5

También puede interbloquear en el bucle de manejo de mensajes de Windows.Cuando ThreadA tenía un bloqueo en CS1 y luego realiza una llamada que requiere que el bucle de mensaje se repita; y el hilo B está en efecto interrumpiendo, interrumpiendo o demorando indefinidamente el ciclo del mensaje mientras espera la adquisición de CS1 ... –

Respuesta

2

No hay suficiente información sobre su sistema para poder decirle definitivamente si su JobManager y su Programador están causando un punto muerto, pero si ambos llaman al mismo método NewJobItem, entonces este no debería ser el problema ya que ambos adquirirán los bloqueos en el mismo orden.

Para su pregunta si su NewJobItem CS.acquire y DoSomething CS.acquire interactúan entre sí: depende. Si el objeto de bloqueo utilizado en ambos métodos es diferente, entonces las dos llamadas no deberían ser independientes. Si es el mismo objeto, depende del tipo de bloqueo. Si los bloqueos son bloqueos reentrantes (por ejemplo, permiten que se llame varias veces desde el mismo hilo y cuente cuántas veces se han adquirido y liberado), entonces esto no debería ser un problema. Por otro lado, si tiene objetos de bloqueo simples que no admiten la reentrada, la liberación CS de DoSomething podría liberar su bloqueo para ese hilo y luego el CallAMethodWithAnotherCriticalSessionInternally se estaría ejecutando sin la protección del bloqueo CS que se adquirió en NewJobItem.

Los bloqueos se producen cuando hay dos o más subprocesos en ejecución y cada subproceso está esperando que otro subproceso termine su trabajo actual antes de que pueda continuar.

Por ejemplo:

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() 

en cuenta que las cerraduras en hilo 2 se adquieren en el orden opuesto a partir de hilos 1. Ahora bien, si el hilo 1 adquiere la lock_a y luego se interrumpe y el hilo 2 ahora se ejecuta y adquiere lock_b y luego comienza a esperar a que lock_a esté disponible antes de que pueda continuar. Luego, el hilo 1 continúa ejecutándose y lo siguiente que hace es tratar de adquirir lock_b, pero ya está ocupado por el hilo 2 y espera. Finalmente, nos encontramos en una situación en la que el subproceso 1 está esperando que el subproceso 2 libere lock_b y el subproceso 2 espere a que el subproceso 1 libere lock_a.

Esto es un interbloqueo.

hay varias soluciones comunes:

  1. sólo utiliza un bloqueo global compartida en todo el código. De esta forma es imposible tener dos hilos esperando dos bloqueos. Esto hace que su código espere mucho para que el bloqueo esté disponible.
  2. Solo permita que su código mantenga un bloqueo a la vez. Esto suele ser demasiado difícil de controlar ya que es posible que no conozca o no controle el comportamiento de las llamadas a métodos.
  3. Solo permita que su código adquiera varios bloqueos al mismo tiempo, y libérelos todos al mismo tiempo, y no permita la adquisición de nuevos bloqueos mientras ya tenga los bloqueos adquiridos.
  4. Asegúrese de que todos los bloqueos se adquieran en el mismo orden global. Esta es una técnica más común.

Con la solución 4. debe tener una programación cuidadosa y siempre asegúrese de adquirir los bloqueos/secciones críticas en el mismo orden. Para ayudar con la depuración puede hacer un pedido global en todos los bloqueos de su sistema (por ejemplo, solo un entero único para cada bloqueo) y luego lanzar un error si intenta adquirir un bloqueo que tiene una clasificación más baja que un bloqueo que el hilo actual ya ha adquirido (por ejemplo. Si new_lock.id < lock_already_acquired.id luego tirar excepción)

Si no se puede poner en una ayuda para la depuración global para ayudar a encontrar a los que las cerraduras han sido adquiridos fuera de orden, el I' Sugiero que encuentre todos los lugares en su código que adquiera un bloqueo y simplemente imprima un mensaje de depuración con la hora actual, el método que llama a adquirir/liberar, el ID del hilo y el ID de bloqueo que se está adquiriendo. Haga también lo mismo con todas las llamadas de lanzamiento. Luego ejecute su sistema hasta que encuentre el punto muerto y encuentre en su archivo de registro qué cerraduras han sido adquiridas por qué hilos y en qué orden. Luego, decide qué hilo está accediendo a sus bloqueos en el orden incorrecto y cámbialo.

Cuestiones relacionadas