Deadlock de un hilo, trylock y livelock
🎯 Al terminar podrás
- Explicar por qué un solo hilo puede hacer deadlock y qué cambia con un mutex ERRORCHECK o RECURSIVE.
- Usar
pthread_mutex_trylocky decir qué condición de Coffman rompe. - Distinguir deadlock, livelock y starvation por tres criterios: ¿ejecutan?, ¿alguien progresa?, ¿se resuelve solo?
- Proponer tres formas de evitar el livelock y elegir la más robusta.
Antes necesitas: Coffman · Mutex POSIX
- Detective escribe tu hipótesis del bug antes de abrir el diagnóstico
- Debate elige una alternativa para el escenario y defiéndela
- Feynman explícalo en voz alta sin mirar y compara con la lista
Repaso rápido: semáforos POSIX (diap. 4–7)
Sección titulada «Repaso rápido: semáforos POSIX (diap. 4–7)»Un semáforo es un contador especial para recursos compartidos (Dijkstra, 1965):
- P() (wait, down): si el contador es 0, bloquea; si no, lo decrementa.
- V() (signal, post, up): nunca bloquea. Despierta a uno si hay alguno esperando; si no, incrementa.
En POSIX (man 7 sem_overview) el valor nunca baja de 0. Hay semáforos con nombre (sem_open("/nombre"), para procesos sin parentesco) y sin nombre (sem_init). Todo el detalle está en Mutex POSIX y semáforos.
Deadlock de un solo hilo (diap. 8–9)
Sección titulada «Deadlock de un solo hilo (diap. 8–9)»void f() { mutex_lock(&lock); mutex_lock(&lock); // ← el mismo hilo espera un lock que él mismo tiene}No hace falta un segundo hilo: el hilo espera a que él mismo suelte el lock, cosa que nunca va a pasar. Con un pthread_mutex_t NORMAL (el predeterminado de Linux), esto se cuelga para siempre. Con ERRORCHECK, el segundo lock retorna EDEADLK; con RECURSIVE, funciona (cuenta 2). Esto se probó en una ejecución de prueba: ver tipos de mutex.
Cuándo pasa en la vida real: una función pública toma el lock y llama a otra función pública que también lo toma. Las diapositivas mencionan dos soluciones (que el curso no profundiza):
- Replicar el código, para que la función interna no tome el lock.
- Función auxiliar no atómica: la versión pública toma el lock y llama a
aux_function1(e), que asume que el lock ya está tomado. Las funciones internas solo llaman a las auxiliares.
void function1(Structure *e) { mutex_lock(&e->lock); aux_function1(e); // trabaja sin tomar el lock mutex_unlock(&e->lock);}pthread_mutex_trylock (diap. 10)
Sección titulada «pthread_mutex_trylock (diap. 10)»«Equivalente a
pthread_mutex_lock(), salvo que si el mutex está tomado (por cualquier hilo, incluido el actual), la llamada retorna de inmediato.»
- Retorna 0 si lo obtuvo, y
EBUSYsi estaba tomado. Nunca bloquea. - Es el equivalente de
sem_trywait(retorna −1 conerrno = EAGAIN), que se usa en el barbero dormilón. - Rompe la condición hold and wait de Coffman: si no puedo tomar el segundo recurso, no me quedo esperando con el primero.
Livelock (diap. 11–14)
Sección titulada «Livelock (diap. 11–14)»void process1(void) { void process2(void) { acquire_lock(&resource_1); acquire_lock(&resource_2); while (try_lock(&resource_2) == FAIL) { while (try_lock(&resource_1) == FAIL) { release_lock(&resource_1); release_lock(&resource_2); wait_fixed_time(); wait_fixed_time(); acquire_lock(&resource_1); acquire_lock(&resource_2); } } use_both_resources(); use_both_resources(); release_lock(&resource_2); release_lock(&resource_1); release_lock(&resource_1); release_lock(&resource_2);} }La secuencia de las diapositivas:
- P1 toma el recurso 1 y P2 toma el recurso 2.
- Cada uno intenta el otro lock y falla. Por cortesía, suelta el que tenía, espera un tiempo fijo y lo vuelve a tomar.
- Como esperan lo mismo, vuelven a chocar exactamente igual… hasta que algo externo rompa la simetría.
«Ningún proceso está bloqueado, así que no es un deadlock. Pero no hay progreso posible: es algo equivalente, un livelock.»
| Deadlock | Livelock | Starvation | |
|---|---|---|---|
| ¿Los procesos ejecutan? | no: bloqueados | sí: gastan CPU | sí (otros avanzan) |
| ¿Alguien progresa? | nadie | nadie | sí, todos menos la víctima |
| ¿Se resuelve solo? | nunca | puede, si cambian los tiempos | puede, si cambian los tiempos |
| Ejemplo | los 5 filósofos con un tenedor cada uno | dos procesos educados con trylock y espera fija | filósofo 0 en tanen-5-1.c |
¿Qué pasa al ejecutarlo de verdad?
Sección titulada «¿Qué pasa al ejecutarlo de verdad?»Programé el ejemplo con pthread_mutex_trylock: los dos hilos arrancan juntos con una barrera, cada uno espera 1 ms con el primer lock tomado, y luego intenta el segundo. Ejecución de prueba, 5 rondas:
== espera fija (1 ms)ronda 0: reintentos P1=0 P2=1ronda 1: reintentos P1=1 P2=0ronda 2: reintentos P1=0 P2=1…== espera aleatoria (0.1–2 ms)ronda 0: reintentos P1=1 P2=0…El livelock no se sostuvo: en todas las rondas, uno de los dos tuvo que reintentar una sola vez. En una máquina real, los tiempos nunca son idénticos: el planificador, las interrupciones y la caché meten ruido que rompe la simetría. Pero eso no prueba que el código sea correcto. El livelock es posible, y en un sistema con temporización muy regular (por ejemplo, un sistema embebido sin otras tareas) puede durar mucho.
Cómo evitarlo de verdad:
- Orden global de los locks: todos toman primero el recurso 1 y luego el 2. Rompe la espera circular y no hace falta
trylock. Es la mejor solución. - Espera aleatoria (randomized backoff), como el retroceso exponencial de Ethernet: hace muy improbable repetir el choque.
- Asimetría: un proceso cede siempre y el otro nunca. Es la idea de los filósofos asimétricos.
Pregunta de análisis: ¿el código con trylock puede tener deadlock? ¿y starvation?
- Deadlock: no. Ningún proceso espera bloqueado sosteniendo un recurso: cuando
try_lockfalla, suelta lo que tiene. Se rompe hold and wait. - Livelock: sí, si ambos repiten la secuencia sincronizados (espera fija e igual).
- Starvation: sí. Aunque no haya livelock, un proceso puede tener mala suerte siempre: el otro vuelve a tomar su lock justo antes de cada
try_lock. Nada garantiza un orden.
Autoevaluación
0/5 correctas · 0 respondidas📈 ¿Qué tan bien lo dominas?
Elige pensando en lo que hiciste, no en lo que leíste. Para marcar 3 tienes que haber resuelto un ejercicio nuevo sin pistas y haberlo justificado. Si solo lo leíste y sientes que «ya entendí», todavía es un 1.
Fuente ·sem_9/11-OSL-Deadlock.pdf (diap. 4–14) · man pthread_mutex_trylock(3), sem_overview(7) · Prueba propia: livelock.c (ejecución de prueba)