Métodos

42 (VIII): philosophers, hilos a la mesa

Filósofos que comen, piensan y mueren de hambre: concurrencia, mutex y deadlocks explicados con espaguetis.

42 (VIII): philosophers, hilos a la mesa

so_long tenía ventana y sprites; philosophers no tiene nada a la vista: un terminal que imprime líneas y, si lo haces mal, un programa que se cuelga o declara muertos que están vivos. Es el problema clásico de los filósofos a la mesa: N filósofos alrededor de una mesa circular, un tenedor entre cada pareja, y para comer hacen falta los dos de al lado. Si uno no come dentro del tiempo dado, muere, y el log debe decirlo sin mezclar líneas ni añadir nada después.

El problema de verdad

Aquí no se ordenan datos: se coordinan hilos. El peligro tiene tres caras. El deadlock: si todos los filósofos cogen primero el tenedor izquierdo, todos se quedan esperando el derecho para siempre. Las condiciones de carrera: el momento de la última comida lo escribe un hilo y lo lee otro; sin protección, de vez en cuando alguien muere por error o sobrevive estando muerto. Y la precisión temporal: usleep no duerme lo que le pides, y medio milisegundo de desfase es la diferencia entre un filósofo vivo y uno muerto.

Cómo lo resolví

Un hilo por filósofo y un mutex por tenedor. Contra el deadlock, romper el círculo: los filósofos pares cogen primero el tenedor derecho y los impares primero el izquierdo; así nadie se queda bloqueado en círculo. Contra las carreras, estado compartido mínimo y protegido: el flag de parada y el log tienen su mutex, y cada filósofo protege su momento de última comida y su contador de comidas con un mutex propio. Un monitor en el hilo principal recorre la mesa cada medio milisegundo comprobando si alguien superó el tiempo o si todos comieron lo que tocaba, y cuando toca pararlo todo, lo para. Para dormir con precisión hice un sleep a trozos pequeños que comprueba la parada, y un desfase inicial entre pares e impares para que no arranquen todos a la vez con la misma hambre. El caso de un solo filósofo, con un único tenedor, va por camino propio: lo coge, el log lo dice, y muere.

Una curiosidad: tengo dos repositorios de este proyecto, philosophers y 42_philosophers. El primero funcionaba, pero lo repetí con la estructura más limpia que había ido aprendiendo; compararlos es ver cómo había cambiado mi manera de partir un problema.

Qué me llevo

Philosophers me dejó una desconfianza sana: el bug de concurrencia no sale cada vez, sale uno de cada cincuenta ejecuciones, así que acabé probando el programa en bucles de cientos de ejecuciones antes de decir que iba bien. También entendí que un mutex no es magia sino disciplina: saber exactamente qué datos toca quién y proteger solo lo que hace falta. Y que los problemas clásicos de los años sesenta y setenta siguen enseñando cosas que ningún tutorial moderno explica tan bien.