Saltar al contenido

Cómo descubrir el interbloqueo y prevenirlo en C #

Solución:

Parece que tuvo problemas para explicar cómo pueden ocurrir los interbloqueos y cómo se pueden prevenir.

Se produce un interbloqueo cuando cada subproceso (mínimo dos) intenta adquirir un bloqueo en un recurso ya bloqueado por otro. El subproceso 1 bloqueado en los recursos 1 intenta adquirir un bloqueo en el recurso 2. Al mismo tiempo, el subproceso 2 tiene un bloqueo en el recurso 2 e intenta adquirir un bloqueo en el recurso 1. Dos subprocesos nunca abandonan sus bloqueos, de ahí el punto muerto ocurre.

La forma más sencilla de evitar un punto muerto es utilizar un valor de tiempo de espera. La clase Monitor (system.Threading.Monitor) puede establecer un tiempo de espera durante la adquisición de un bloqueo.

Ejemplo

try{
    if(Monitor.TryEnter(this, 500))
    {
        // critical section
    }
}
catch (Exception ex)
{

}
finally
{
    Monitor.Exit();
}

Lee mas

Las herramientas de análisis de rendimiento también pueden ser útiles para identificar puntos muertos, entre otros. Esta pregunta proporcionará información sobre este tema: Herramienta de análisis C # / .NET para encontrar condiciones de carrera / puntos muertos.

El análisis visual del código y el uso adecuado de los bloqueos también es útil (debería poder detectar problemas potenciales en el código mientras lo inspecciona), pero puede ser muy difícil para aplicaciones complejas. A veces, los puntos muertos son visibles solo cuando ejecuta el código, no simplemente inspeccionando el código.

No conozco demasiado a tu entrevistador. Es posible que algunos quieran ver cuánto sabe sobre las normas / pautas de bloqueo, algunos pueden querer ver si sabe cómo usar sus herramientas, algunos pueden querer ambas. En la empresa en la que trabajo, por ejemplo, el uso de herramientas (especialmente las que ya poseemos y usamos) es muy apreciado. Pero eso no implica que uno no deba tener las habilidades que evitarían los interbloqueos de codificación en primer lugar.

Bloquear algo solo por bloquear afecta el rendimiento, ya que los subprocesos se esperan unos a otros. Debe analizar el flujo de trabajo para determinar qué es lo que realmente debe bloquearse, cuándo, con qué tipo de bloqueo (simple lock o tal vez un ReaderWriterLockSlim). Hay muchas formas típicas de evitar un punto muerto.

Por ejemplo al usar ReaderWriterLockSlim puede usar un tiempo de espera para evitar interbloqueos (si espera demasiado, cancela la obtención del bloqueo)

if (cacheLock.TryEnterWriteLock(timeout))
{
...
}

Y debería poder sugerir tales tiempos de espera.

Ante tal pregunta, esperaría al menos una mención del caso clásico de interbloqueos, como el mal uso de bloqueos anidados (debería poder saber si puede evitarlos, por qué son malos, etc.).

El tema es muy grande … puedes seguir hablando de esto. Pero sin definiciones. Saber qué es un bloqueo y saber usar bloqueos / semáforos / mutex en aplicaciones de subprocesos múltiples a gran escala son dos cosas diferentes.

Creo que la entrevista te hace una pregunta capciosa. Si pudiera utilizar el análisis estático para evitar un punto muerto … ¡nadie tendría un punto muerto!

Personalmente, cuando busco un punto muerto, empiezo por encontrar funciones en las que la sección crítica abarca más que la llamada a la función. Por ejemplo

void func(){
    lock(_lock){
        func2();
     }
}

No está muy claro qué func2 está haciendo. Tal vez distribuya un evento en el mismo hilo, lo que significaría que el evento sigue siendo parte de la sección crítica. Tal vez luego se bloquee en un diferente cerrar con llave. ¡Tal vez se envíe al grupo de subprocesos y ya no sea reentrante porque ahora está en un subproceso diferente! Este tipo de lugares es donde puede comenzar a ver situaciones de interbloqueo: cuando tiene múltiples ubicaciones de bloqueo no reentrante.

Otras veces, al rastrear escenarios de interbloqueo, retrocedo y trato de encontrar dónde se crean todos los hilos. Pienso en cada función y en dónde se puede ejecutar. Si no está seguro, agregar el registro para registrar de dónde provino la llamada a la función también puede ayudar.

También puede evitar los puntos muertos mediante el uso de estructuras de datos sin bloqueos (pero requieren la misma cantidad de tiempo para su uso). Desea minimizar su acceso a la estructura libre de cerraduras porque cada vez que accede a ella puede cambiar.

Como se mencionó en otra respuesta, puede usar mutex con tiempos de espera, pero no se garantiza que siempre funcione (¿qué pasa si su código necesita funcionar más tiempo que el tiempo de espera?). En otro comentario se mencionó que esto es quizás lo que estaba pidiendo el entrevistador. Encuentro que en producción esto no es realmente una buena idea. Los tiempos de espera varían todo el tiempo, tal vez algo tardó más de lo esperado en ejecutarse y alcanzó un tiempo de espera. Creo que es mejor dejarlo en un punto muerto, realizar un volcado de proceso, luego encontrar exactamente qué estaba bloqueando y solucionar el problema. Por supuesto, si los requisitos de su empresa no pueden permitirlo, puede utilizarlo como parte de una estrategia de codificación defensiva junto con las opciones de colocación de cerraduras inteligentes.

No estoy de acuerdo con tu entrevista que cierra siempre agregar un gran problema de rendimiento. Los bloqueos / mutex / etc. no controlados se prueban primero como un bloqueo de giro antes de pasarlo al sistema operativo y los bloqueos de giro son baratos.

En general, la mejor manera de evitar el estancamiento es comprender el flujo de su programa. Cada vez que introduzca un nuevo objeto de bloqueo, piense dónde se usa y qué lo usa en la cadena.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *