← Concurrencia y Sincronización Bloque 2 · La sección crítica · Tema 6 de 47

Tema 6

Sección crítica y exclusión mutua

En el tema anterior viste que un contador compartido da resultados incorrectos porque counter++ no es atómico. La solución general no es hacer atómica cada operación, sino identificar qué fragmentos de código no pueden ejecutarse simultáneamente y protegerlos. Ese fragmento se llama sección crítica, y la propiedad que se exige es la exclusión mutua: a lo sumo un hilo a la vez. En este tema verás qué es una sección crítica, cómo se estructura la protección y por qué es la base de toda la sincronización.

1. Qué es una sección crítica

Una sección crítica (critical section) es un fragmento de código que accede a un recurso compartido (una variable, una estructura de datos, un dispositivo) y que no debe ejecutarse simultáneamente por más de un hilo o proceso. Si dos hilos entran a la vez en sus secciones críticas, el resultado puede ser incorrecto.

La sección crítica es el concepto central de la sincronización. Toda herramienta de sincronización (mutex, semáforo, operación atómica) existe para proteger secciones críticas: garantizar que solo un hilo a la vez las ejecute.

La idea es simple: si un fragmento de código modifica un dato compartido, solo un hilo puede ejecutarlo a la vez. Los demás deben esperar. Cuando el hilo termina, otro puede entrar.

La sección crítica es el qué, no el cómo

La sección crítica define qué código debe protegerse. El cómo (mutex, semáforo, operación atómica) se estudia en los bloques siguientes. Primero hay que identificar la sección crítica; después, elegir la herramienta.

2. Ejemplos de secciones críticas

Una sección crítica puede ser tan simple como un incremento o tan compleja como una transacción bancaria:

ejemplos de secciones críticas
/* Ejemplo 1: contador compartido (la más simple). */
counter++;                          /* sección crítica: leer-modificar-escribir */

/* Ejemplo 2: inserción en una lista enlazada. */
nuevo->siguiente = lista;           /* sección crítica: modificar la estructura */
lista = nuevo;

/* Ejemplo 3: transferencia bancaria. */
cuenta_origen.saldo -= monto;       /* sección crítica: dos operaciones */
cuenta_destino.saldo += monto;      /* que deben ser atómicas en conjunto */

/* Ejemplo 4: actualización de un caché. */
cache[clave] = valor;               /* sección crítica: modificar el caché */
cache_size++;

/* Ejemplo 5: registro de eventos. */
log[log_count++] = evento;          /* sección crítica: índice y escritura */

En todos estos casos, si dos hilos ejecutan el código simultáneamente, el resultado puede ser incorrecto: un incremento perdido, una lista corrupta, un saldo negativo, un caché inconsistente, un log con entradas desordenadas.

La sección crítica no es siempre una sola línea. Puede ser un bloque de varias líneas que, en conjunto, deben ejecutarse de forma indivisible. Por ejemplo, la transferencia bancaria tiene dos operaciones que deben ser atómicas en conjunto: si se interrumpe entre ellas, el dinero desaparece.

3. Qué es la exclusión mutua

La exclusión mutua (mutual exclusion) es la propiedad que garantiza que, cuando un hilo está ejecutando su sección crítica, ningún otro hilo puede ejecutar su sección crítica sobre el mismo recurso. Es la propiedad más básica de la sincronización.

La exclusión mutua no significa que los hilos no puedan ejecutarse simultáneamente en general: solo que no pueden ejecutar sus secciones críticas simultáneamente. El resto del código (la sección no crítica) puede ejecutarse en paralelo sin restricciones.

La exclusión mutua se implementa con mecanismos de sincronización: mutex, semáforos, operaciones atómicas. Cada mecanismo proporciona un protocolo de entrada (para entrar a la sección crítica) y un protocolo de salida (para salir de ella).

4. La estructura del protocolo

Todo programa que usa secciones críticas tiene esta estructura:

estructura del protocolo
while (true) {
    /* Sección no crítica: trabajo independiente. */
    hacer_trabajo_independiente();

    /* Protocolo de entrada: pedir permiso para entrar. */
    entrar_seccion_critica();

    /* Sección crítica: acceso al recurso compartido. */
    acceder_recurso_compartido();

    /* Protocolo de salida: liberar el permiso. */
    salir_seccion_critica();

    /* Sección no crítica: más trabajo independiente. */
    hacer_mas_trabajo();
}

Las cuatro partes son:

ParteDescripciónEjemplo
Sección no críticaTrabajo que no accede al recurso compartido. Puede ejecutarse en paralelo sin restricciones.Calcular datos locales, procesar resultados.
Protocolo de entradaPedir permiso para entrar a la sección crítica. Puede bloquear si otro hilo está dentro.mutex_lock(&m), sem_wait(&s).
Sección críticaAcceso al recurso compartido. Solo un hilo a la vez.counter++, insertar en lista.
Protocolo de salidaLiberar el permiso para que otro hilo pueda entrar.mutex_unlock(&m), sem_post(&s).

El protocolo de entrada y salida es la parte que se implementa con mecanismos de sincronización. La sección crítica es el código que se protege. La sección no crítica es el código que no necesita protección.

5. Sección crítica vs. región crítica

A veces se confunden los términos «sección crítica» y «región crítica». La diferencia es:

La exclusión mutua garantiza que, en cualquier momento, haya a lo sumo una región crítica activa por recurso compartido. Es decir, a lo sumo un hilo ejecutando su sección crítica.

6. Cuándo hace falta

Se necesita una sección crítica cuando:

Si cualquiera de estas condiciones no se cumple, no hay sección crítica y no se necesita sincronización.

7. Cuándo no hace falta

No se necesita una sección crítica cuando:

En el experimento de calculo.c (tema 1), no había sección crítica porque cada hilo usaba su propia variable local. En compartir.c (tema 2), sí había sección crítica porque todos los hilos modificaban la misma variable.

8. El problema visualizado

Este diagrama muestra el problema de la sección crítica: dos hilos entran a la vez y el resultado es incorrecto:

Dos hilos entran a la sección crítica simultáneamente, causando una condición de carrera. SIN EXCLUSIÓN MUTUA (condición de carrera) Hilo A Hilo B no crit. SECCIÓN CRÍTICA no crit. no crit. SECCIÓN CRÍTICA no crit. Ambos hilos están en la sección crítica a la vez: condición de carrera. CON EXCLUSIÓN MUTUA (correcto) Hilo A Hilo B no crit. SECCIÓN CRÍTICA no crit. no crit. SECCIÓN CRÍTICA no crit. La exclusión mutua serializa la sección crítica: B espera a que A termine.
Sin exclusión mutua, ambos hilos entran a la vez. Con exclusión mutua, la sección crítica se serializa.

9. Primeros intentos (adelanto)

En los temas siguientes se estudiarán soluciones a la exclusión mutua. Los primeros intentos fallan por razones distintas:

Estos intentos se estudian en detalle en los temas 8 y 9. El punto importante aquí es que la exclusión mutua no es trivial de implementar: requiere mecanismos que garanticen la atomicidad de la entrada.

10. Secciones críticas en Linux

El núcleo de Linux usa secciones críticas internamente para proteger sus estructuras de datos. Los mecanismos son:

En el espacio de usuario, las secciones críticas se protegen con mutexes de pthreads, semáforos POSIX o operaciones atómicas de C11.

11. Resumen

12. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué es una sección crítica?

Un fragmento de código que accede a un recurso compartido y no debe ejecutarse simultáneamente por más de un hilo.

2. ¿Qué es la exclusión mutua?

La propiedad que garantiza que solo un hilo a la vez ejecute su sección crítica sobre un recurso compartido.

3. ¿Cuáles son las cuatro partes del protocolo?

Sección no crítica, protocolo de entrada, sección crítica, protocolo de salida.

4. ¿Cuándo se necesita una sección crítica?

Cuando hay un recurso compartido, al menos un hilo lo modifica y la operación no es atómica.

5. ¿Cuándo no se necesita una sección crítica?

Cuando no hay recurso compartido, no hay modificación o la operación es atómica.

6. ¿Cuál es la diferencia entre sección crítica y región crítica?

La sección crítica es el código (estático); la región crítica es la ejecución (dinámica).

7. ¿La exclusión mutua impide que los hilos se ejecuten en paralelo?

No. Solo serializa la sección crítica. El resto del código puede ejecutarse en paralelo.

8. ¿Es trivial implementar la exclusión mutua?

No. Requiere mecanismos que garanticen la atomicidad de la entrada.

9. ¿Qué mecanismos usa Linux para secciones críticas en el núcleo?

Spinlocks, mutexes, semáforos y RCU.

10. ¿Qué mecanismos se usan en el espacio de usuario?

Mutexes de pthreads, semáforos POSIX y operaciones atómicas de C11.

Para practicar

  1. Identifica la sección crítica en el programa compartir.c del tema 2.
  2. Identifica la sección crítica en un programa que inserta en una lista enlazada.
  3. Determina si una operación de lectura de un byte necesita sección crítica.
  4. Escribe un programa con una sección crítica que accede a un archivo compartido.
  5. Dibuja el diagrama de dos hilos con y sin exclusión mutua.
  6. Investiga qué es RCU y cuándo se usa en Linux.