← Concurrencia y Sincronización Bloque 8 · Interbloqueos · Tema 37 de 47

Tema 37

Prevención: orden de bloqueo

La prevención de interbloqueos consiste en diseñar el programa de forma que no se puedan cumplir las cuatro condiciones de Coffman simultáneamente. La técnica más usada es el orden de bloqueo: asignar un orden total a los recursos y exigir que se adquieran en ese orden. En este tema verás cómo romper cada condición y por qué el orden de bloqueo es la técnica principal.

1. Qué es la prevención

La prevención de interbloqueos consiste en diseñar el programa de forma que no se puedan cumplir las cuatro condiciones de Coffman simultáneamente. Si se rompe al menos una condición, no puede haber deadlock, sin importar el orden de ejecución.

La prevención es una técnica estática: se aplica en tiempo de diseño, no en tiempo de ejecución. El programador diseña el código de forma que sea imposible que se produzca un deadlock.

La prevención tiene un costo: puede reducir el rendimiento o la flexibilidad del programa. Pero garantiza que no habrá deadlocks, lo que es esencial para sistemas críticos.

2. Romper las condiciones de Coffman

Cada condición se puede romper de una forma específica:

CondiciónCómo romperlaTécnica
Exclusión mutuaHacer los recursos compartibles.Raramente aplicable.
Retención y esperaSolicitar todos los recursos de una vez, o liberar antes de solicitar.Asignación atómica.
No apropiaciónPermitir quitar recursos a un proceso.Preemption.
Espera circularImponer un orden total en la adquisición de recursos.Orden de bloqueo.

3. Romper la exclusión mutua

La exclusión mutua se rompe haciendo los recursos compartibles: permitir que varios procesos los usen simultáneamente. Por ejemplo, un archivo en modo lectura puede ser compartido por varios procesos.

Sin embargo, esto no es aplicable a recursos que inherentemente requieren exclusión mutua, como los mutexes. No se puede compartir un mutex entre varios hilos: su propósito es la exclusión mutua.

Por eso, esta técnica se usa raramente para prevenir deadlocks.

4. Romper la retención y espera

La retención y espera se rompe de dos formas:

La primera forma se llama asignación atómica de recursos. Es efectiva pero puede ser ineficiente: un proceso puede retener recursos que no necesita mientras espera otros.

asignación atómica
/* Solicitar todos los recursos de una vez. */
void adquirir_recursos(int *recursos, int n) {
    /* Intentar adquirir todos. */
    for (int i = 0; i < n; i++) {
        if (!try_lock(recursos[i])) {
            /* No se pudo adquirir todos: liberar los adquiridos. */
            for (int j = 0; j < i; j++) {
                unlock(recursos[j]);
            }
            /* Esperar y reintentar. */
            wait_and_retry(recursos, n);
            return;
        }
    }
    /* Todos adquiridos. */
}

5. Romper la no apropiación

La no apropiación se rompe permitiendo quitar recursos a un proceso. Si un proceso tiene un recurso y otro proceso de mayor prioridad lo necesita, el sistema puede quitarle el recurso al primero.

Esto es complejo porque:

La apropiación se usa en sistemas operativos para la CPU (el planificador puede quitarle la CPU a un proceso), pero rara vez para otros recursos.

6. Romper la espera circular

La espera circular se rompe imponiendo un orden total en la adquisición de recursos. Si los recursos se numeran y los procesos los adquieren en orden creciente, no puede haber un ciclo.

Esta es la técnica más usada y la más práctica. Se llama orden de bloqueo (lock ordering).

El orden de bloqueo es la técnica principal

El orden de bloqueo es la técnica de prevención más usada en la práctica. Es simple de implementar y efectiva. La única dificultad es que todos los programadores deben respetar el mismo orden.

7. El orden de bloqueo

El orden de bloqueo (lock ordering) consiste en asignar un número a cada recurso (mutex, semáforo, etc.) y exigir que los procesos los adquieran en orden creciente. Si un proceso tiene el recurso R5, solo puede adquirir R6 o superior, nunca R4 o inferior.

El orden de bloqueo rompe la espera circular porque no puede haber un ciclo: si todos adquieren en orden creciente, la cadena de espera siempre va hacia arriba, nunca vuelve al principio.

La implementación requiere:

En la práctica, el orden se suele asignar por la dirección de memoria del mutex (menor dirección = mayor prioridad) o por un nombre explícito.

8. Un ejemplo desarrollado

El ejemplo del tema 35 (dos hilos, dos mutexes) se arregla con orden de bloqueo:

orden de bloqueo
#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdio.h>

/* Asignar orden: mutex_a = 1, mutex_b = 2. */
pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;  /* orden 1 */
pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;  /* orden 2 */

void *hilo_1(void *arg) {
    /* Adquirir en orden: a (1) luego b (2). */
    pthread_mutex_lock(&mutex_a);
    pthread_mutex_lock(&mutex_b);
    /* ... usar ambos ... */
    pthread_mutex_unlock(&mutex_b);
    pthread_mutex_unlock(&mutex_a);
    return NULL;
}

void *hilo_2(void *arg) {
    /* Adquirir en el mismo orden: a (1) luego b (2). */
    pthread_mutex_lock(&mutex_a);  /* antes era mutex_b primero */
    pthread_mutex_lock(&mutex_b);
    /* ... usar ambos ... */
    pthread_mutex_unlock(&mutex_b);
    pthread_mutex_unlock(&mutex_a);
    return NULL;
}

Con el mismo orden de adquisición, no puede haber deadlock: si Hilo 2 quiere mutex_a y Hilo 1 lo tiene, Hilo 2 espera. Cuando Hilo 1 libera mutex_a, Hilo 2 lo adquiere y luego adquiere mutex_b (que Hilo 1 ya liberó). No hay ciclo.

9. Ventajas y desventajas

TécnicaVentajaDesventaja
Orden de bloqueoSimple, efectiva, sin overhead en tiempo de ejecución.Requiere disciplina de todos los programadores. Difícil de mantener en código grande.
Asignación atómicaPreviene deadlock completamente.Puede retener recursos innecesariamente. Ineficiente.
ApropiaciónPermite quitar recursos a procesos.Compleja, puede causar inconsistencias.
Compartir recursosElimina la exclusión mutua.No aplicable a todos los recursos.

El orden de bloqueo es la técnica más usada en la práctica porque es simple y no tiene overhead. La única dificultad es mantener la disciplina en equipos grandes.

10. Prevención en Linux

Linux usa varias técnicas de prevención:

lockdep es una herramienta invaluable para detectar deadlocks en el núcleo. Se activa con CONFIG_LOCKDEP y verifica el orden de todos los locks en tiempo de ejecución.

11. Simulador interactivo

Este simulador muestra cómo el orden de bloqueo previene el deadlock:

Simulador · orden de bloqueo

12. Resumen

13. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué es la prevención de interbloqueos?

Diseñar el programa para que no se puedan cumplir las cuatro condiciones de Coffman simultáneamente.

2. ¿Cuál es la técnica de prevención más usada?

El orden de bloqueo (lock ordering).

3. ¿Cómo rompe el orden de bloqueo la espera circular?

Imponiendo un orden total en la adquisición de recursos. No puede haber un ciclo si todos adquieren en orden creciente.

4. ¿Qué es la asignación atómica de recursos?

Solicitar todos los recursos de una vez, o liberar antes de solicitar nuevos.

5. ¿Qué condición rompe la asignación atómica?

La retención y espera.

6. ¿Qué es lockdep?

Una herramienta del núcleo de Linux que detecta violaciones del orden de bloqueo.

7. ¿Por qué la apropiación es difícil de implementar?

Porque el proceso al que se le quitó el recurso puede estar en un estado inconsistente.

8. ¿Por qué la exclusión mutua es difícil de romper?

Porque los recursos que requieren exclusión mutua no se pueden compartir.

9. ¿Qué desventaja tiene el orden de bloqueo?

Requiere disciplina de todos los programadores. Difícil de mantener en código grande.

10. ¿Qué técnica usa Linux para verificar el orden de bloqueo?

lockdep (CONFIG_LOCKDEP).

Para practicar

  1. Modifica el programa de deadlock del tema 35 para usar orden de bloqueo.
  2. Implementa la asignación atómica de recursos.
  3. Explica por qué el orden de bloqueo rompe la espera circular.
  4. Investiga cómo funciona lockdep en el núcleo de Linux.
  5. Diseña un esquema de orden de bloqueo para un programa con 5 mutexes.
  6. Explica por qué la exclusión mutua es la condición más difícil de romper.