Hay abrazos que matan

Si hiciéramos un ránking de los programas más complejos de escribir (bien) del mundo, posiblemente en primer lugar aparecerían los sistemas operativos, seguidos de los gestores de bases de datos y algún que otro videojuego especialmente elaborado.

Algunos sistemas operativos se mueven en los límites de la complejidad que los programadores humanos son capaces de manejar en la actualidad. Son productos de tecnología punta, como las naves espaciales o los teléfonos móviles de última generación.

El desarrollo de sistemas operativos se enfrenta con muchos problemas de difícil solución: lidiar directamente con el hardware, que suele comportarse como un niño rico consentido; repartir los recursos, siempre escasos, entre los programas en ejecución, poco dados a la generosidad mutua; asegurar la estabilidad y eficiencia del sistema; ofrecer un interfaz sólido y al mismo tiempo flexible a las aplicaciones de usuario; y un largo etcétera.

Una de las características de cualquier sistema operativo moderno es que son multitarea, esto eso, permiten la ejecución simultánea de varios procesos: puedo estar trabajando con mi procesador de textos mientras navego por internet y estoy extrayendo el audio de un CD, por ejemplo. Ahí existen, como mínimo, tres procesos tratando de usar al mismo tiempo la memoria, la pantalla, el disco duro y una larga lista de recursos más o menos exóticos. Y el sistema operativo debe ejercer de árbitro para que todo vaya como la seda.

A veces, un proceso (llamémoslo A) necesita un recurso (por ejemplo, la conexión de red) que está siendo usado por otro proceso (llamémoslo B) en ese momento. El proceso A se queda esperando cruzado de brazos a que el recurso se libere para poder usarlo. Sólo cuando el recurso es liberado por B, el proceso A puede empezar a usarlo y continuar de ese modo con su ejecución como si no hubiera pasado nada.

Pueden imaginarse una situación castrófica: por pura mala suerte, puede ocurrir el proceso B necesite un recurso que esté siendo usado por el proceso A. Por ejemplo, imaginemos que A está usando el CDROM y que B lo necesita imperativamente para continuar funcionando. Y ya la hemos liado, porque ahora el proceso B se detiene a la espera de obtener el CDROM, que está en poder del proceso A, que a su vez está detenido a la espera de que se libere la conexión de red, que está siendo usada por B. Esta situación de bloqueo mutuo se denomina deadlock, que suele españolizarse como interbloqueo, aunque a mí me gusta más la ligeramente poética traducción de abrazo mortal. Me imagino a los procesos indecisos, sin saber qué hacer, fundidos en un abrazo digital y esperando eternamente el recurso que nunca llegará a menos que el sistema operativo decida fulminarlos a los dos, por tontainas.

Esta situación, aunque pueda parecer extrema, no es demasiado infrecuente, y los sistemas operativos tienen diferentes estrategias para enfrentarse a ella: desde la estrategia Homer Simpson (acostarse para ver si por la mañana el problema se ha solucionado solo), hasta el asesinato sin contemplaciones de los procesos implicados.

Hay una metáfora clásica para ilustrar este problema, llamada comúnmente el problema de los filósofos cenando. Imaginen a cinco filósofos que se sientan alrededor de una mesa con intención de cenar. Cada filósofo tiene un plato de spaghetti (¿no es una metáfora encantadora?) y un tenedor a la izquierda de su plato. Pero para comer los spaghetti nuestros filósofos, poco habituados a las mezquindades de la vida material, necesitan dos tenedores. Cada filósofo puede coger el tenedor de su izquierda o de su derecha, uno después de otro, pero no puede tomar ambos de golpe.



Si cualquier filósofo coge un tenedor y el otro está ocupado, se quedará esperando, haciendo silogismos mentalmente, con el tenedor en la mano, hasta que pueda coger el otro tenedor para empezar a comer. Por eso los comensales son filósofos y no pastores de cabras: necesitamos que se queden pensando en las musarañas mientras esperan que se libere el segundo tenedor para que nuestra metáfora sea efectiva.

Es fácil ver que, si dos filósofos adyacentes intentan tomar el mismo tenedor a una vez (dos procesos compitiendo por un recurso), uno de ellos se quedará sin comer y acabará muriendo de inanición.

Pero la catástrofe ocurre si todos los filósofos cogen el tenedor que está a su derecha al mismo tiempo: entonces, todos se quedarán esperando eternamente hasta morir de hambre, ya que nadie liberará el tenedor que le hace falta al compañero de al lado. Nadie lo hará porque todos se encuentran en la misma situación. Los filósofos han entrado en un estado de interbloqueo u orgiástico abrazo mortal.

¿Cómo podríamos diseñar un algoritmo que rompiese el abrazo mortal? Más aún, ¿cómo podríamos diseñar un algoritmo que detecte que se ha producido un abrazo mortal entre nuestros filósofos-procesos? Le recomiendo que piense en ello como un excelente ejercicio mental si usted es estudiante de programación o si simplemente quiere ejercitar las neuronas un rato.

Comentarios