Condiciones de carrera y concurrencia
¿Quién pasa primero? ¿Qué pasa si chocan?
Hoy vamos a pensar sobre estos problemas, no a programarlos todavía.
Un proceso es un programa en ejecución.
Cada proceso tiene su propia memoria separada.
Un hilo es una tarea dentro de un proceso.
Diferencia clave: los hilos de un mismo proceso comparten recursos entre sí.
PROCESO = Casa
├── HILO 1 = Persona cocinando
├── HILO 2 = Persona limpiando
└── HILO 3 = Persona estudiando
Todos en la misma casa (comparten cocina, baño, living)
→ ¡Si dos quieren usar el horno, hay que coordinarse!
Un chef hace múltiples tareas intercalándolas:
Minuto 1-2: Pica cebolla
Minuto 3-4: Revisa el horno
Minuto 5-6: Sigue picando
Minuto 7-8: Revisa el horno de nuevo
→ Una persona, múltiples tareas, se va turnando.
Dos chefs trabajan al mismo tiempo:
Chef A: Pica cebolla continuamente
Chef B: Vigila el horno continuamente
→ Dos personas, dos tareas, en verdadero paralelo.
La mayoría de las veces en este curso hablamos de concurrencia: cómo se turnan los procesos para compartir un recurso.
Una condición de carrera (race condition) ocurre cuando el resultado depende del orden de ejecución.
El resultado varía según quién llegue primero.
Situación: 1 asiento libre en la sala
Persona A: Persona B:
1. Mira la pantalla 1. Mira la pantalla
2. Ve "1 asiento libre" 2. Ve "1 asiento libre"
3. Decide comprar 3. Decide comprar
4. Presiona "Comprar" 4. Presiona "Comprar"
→ ¡El sistema vendió 2 entradas para 1 asiento!
María y José comparten una cuenta con $1000.
Depósito de María (+$500):
1. Leer saldo actual → $1000
2. Calcular nuevo saldo → $1000 + $500 = $1500
3. Guardar nuevo saldo → $1500
Retiro de José (-$300):
1. Leer saldo actual → $1000
2. Calcular nuevo saldo → $1000 - $300 = $700
3. Guardar nuevo saldo → $700
t=0: SALDO = $1000
t=1: María lee saldo → obtiene $1000
t=2: José lee saldo → obtiene $1000 (¡María aún no escribió!)
t=3: María calcula → $1000 + $500 = $1500
t=4: José calcula → $1000 - $300 = $700
t=5: María guarda → SALDO = $1500
t=6: José guarda → SALDO = $700
→ ¡El depósito de María se perdió!
Resultado esperado: $1200 | Resultado real: $700
La operación saldo = saldo + 500 NO es atómica. Necesita 3 pasos:
1. LEER el valor actual
2. SUMAR
3. ESCRIBIR el resultado
Si dos procesos leen al mismo tiempo, se sobrescriben mutuamente.
contador = 0
Persona A: hacer 1000 veces { contador = contador + 1 }
Persona B: hacer 1000 veces { contador = contador + 1 }
¿Resultado esperado? 2000
¿Resultado real? Cualquier número entre 1000 y 2000
Cada incremento pierde datos por el mismo problema de lectura/escritura entrelazada.
Una sección crítica es la parte del código donde se accede a un recurso compartido.
Regla de oro: Solo UN proceso/hilo puede ejecutar la sección crítica a la vez.
SECCIÓN CRÍTICA = Estar dentro del baño
Protocolo:
1. Intentar entrar (verificar si está libre)
2. Si está libre → Entrar y CERRAR CON LLAVE
3. Usar el baño
4. Salir y ABRIR LA PUERTA
La llave = Mecanismo de sincronización
PROTOCOLO DE ENTRADA
(pedir permiso, esperar si es necesario)
SECCIÓN CRÍTICA
(acceder al recurso compartido)
PROTOCOLO DE SALIDA
(liberar el recurso para otros)
RESTO DEL CÓDIGO
(trabajo que no necesita el recurso)
Un semáforo es un guardia que controla cuántas personas pueden entrar.
ESTACIONAMIENTO CON 3 ESPACIOS
Semáforo iniciado con valor = 3
Auto 1: ESPERAR → contador baja a 2 → Entra
Auto 2: ESPERAR → contador baja a 1 → Entra
Auto 3: ESPERAR → contador baja a 0 → Entra
Auto 4: ESPERAR → contador = 0 → ¡DEBE ESPERAR!
Auto 1 sale: SEÑALAR → contador sube a 1 → Auto 4 puede entrar
Semáforo Binario (Mutex)
Semáforo Contador
SEMÁFORO mutex = 1 (inicialmente libre)
Depósito:
1. WAIT(mutex) ← Pedir permiso
2. Leer saldo
3. Calcular nuevo saldo
4. Guardar nuevo saldo
5. SIGNAL(mutex) ← Liberar para otros
Retiro:
1. WAIT(mutex) ← Pedir permiso
2. Leer saldo
3. Calcular nuevo saldo
4. Guardar nuevo saldo
5. SIGNAL(mutex) ← Liberar para otros
Ahora NO puede haber race condition porque solo uno accede a la vez.
Cinco filósofos sentados en una mesa circular. Entre cada par hay un palito. Para comer, cada filósofo necesita DOS palitos.
REPETIR SIEMPRE:
- PENSAR (no necesita palitos)
- Tomar palito izquierdo
- Tomar palito derecho
- COMER
- Soltar palito izquierdo
- Soltar palito derecho
¿Qué pasa si todos toman su palito izquierdo "a la vez"?
t=1: F1 toma Palito 1
t=2: F2 toma Palito 2
t=3: F3 toma Palito 3
t=4: F4 toma Palito 4
t=5: F5 toma Palito 5
→ ¡Todos esperan el palito derecho que ya está tomado!
→ DEADLOCK: Nadie puede avanzar, todos bloqueados
Imagina que F1 y F2 comen muy rápido y se turnan constantemente.
F3, F4 y F5 nunca logran conseguir ambos palitos.
→ Inanición: algunos filósofos esperan infinitamente mientras otros se turnan.
Solución 1: Orden asimétrico
Filósofos impares (F1, F3, F5): Primero izquierdo, luego derecho
Filósofos pares (F2, F4): Primero derecho, luego izquierdo
→ Rompe la simetría, previene deadlock
Solución 2: Máximo de comensales
Máximo 4 filósofos pueden intentar comer simultáneamente
→ Siempre al menos uno podrá tomar ambos palitos
Situación donde dos o más procesos están bloqueados permanentemente, esperando recursos que otros tienen.
CRUCE DE CALLES:
Auto A viene del Norte, quiere ir al Sur
Auto B viene del Este, quiere ir al Oeste
Ambos llegan al cruce simultáneamente:
→ A espera que B avance
→ B espera que A avance
→ DEADLOCK: Ninguno puede avanzar
Para que haya deadlock, deben cumplirse las 4: