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 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:
/* 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:
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:
| Parte | Descripción | Ejemplo |
|---|---|---|
| Sección no crítica | Trabajo que no accede al recurso compartido. Puede ejecutarse en paralelo sin restricciones. | Calcular datos locales, procesar resultados. |
| Protocolo de entrada | Pedir 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ítica | Acceso al recurso compartido. Solo un hilo a la vez. | counter++, insertar en lista. |
| Protocolo de salida | Liberar 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:
- Sección crítica: el fragmento de código que accede al recurso compartido. Es estático: está en el código fuente.
- Región crítica: la ejecución de la sección crítica por un hilo en un momento dado. Es dinámica: depende de cuándo y qué hilo ejecuta el código.
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:
- Hay un recurso compartido: una variable, una estructura de datos, un archivo, un dispositivo.
- Al menos un hilo lo modifica: si todos solo leen, no hay sección crítica.
- La modificación no es atómica: si la operación es atómica por hardware, no se necesita sección crítica.
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:
- No hay recurso compartido: cada hilo trabaja con sus propios datos (variables locales, pila).
- No hay modificación: todos los hilos solo leen el dato (datos de solo lectura).
- La operación es atómica: el hardware garantiza que la operación se ejecuta de forma indivisible (por ejemplo, leer o escribir un byte alineado en x86).
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:
9. Primeros intentos (adelanto)
En los temas siguientes se estudiarán soluciones a la exclusión mutua. Los primeros intentos fallan por razones distintas:
- Variable de turno: un hilo cede el turno al otro. Falla porque un hilo puede bloquearse indefinidamente si el otro no quiere entrar.
- Bandera de intención: cada hilo levanta una bandera antes de entrar. Falla porque ambos pueden levantar la bandera a la vez y entrar simultáneamente.
- Algoritmo de Peterson: combina turno y bandera. Funciona para dos hilos, pero no escala a más y no funciona en procesadores modernos sin barreras de memoria.
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:
- Spinlocks: para secciones críticas cortas en código del núcleo.
- Mutexes: para secciones críticas largas que pueden bloquearse.
- Semáforos: para contar recursos.
- RCU (Read-Copy-Update): para datos de solo lectura frecuente.
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
- Una sección crítica es un fragmento de código que accede a un recurso compartido y no debe ejecutarse simultáneamente por más de un hilo.
- La exclusión mutua garantiza que solo un hilo a la vez ejecute su sección crítica.
- El protocolo tiene cuatro partes: sección no crítica, entrada, sección crítica y salida.
- Se necesita sección crítica cuando hay un recurso compartido, al menos un hilo lo modifica y la operación no es atómica.
- No se necesita cuando no hay recurso compartido, no hay modificación o la operación es atómica.
- La sección crítica es estática (código); la región crítica es dinámica (ejecución).
- Implementar la exclusión mutua no es trivial: requiere mecanismos que garanticen la atomicidad de la entrada.
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
- Identifica la sección crítica en el programa
compartir.cdel tema 2. - Identifica la sección crítica en un programa que inserta en una lista enlazada.
- Determina si una operación de lectura de un byte necesita sección crítica.
- Escribe un programa con una sección crítica que accede a un archivo compartido.
- Dibuja el diagrama de dos hilos con y sin exclusión mutua.
- Investiga qué es RCU y cuándo se usa en Linux.