Una condición de carrera (en inglés, race condition) es un fallo que aparece cuando el resultado de un programa depende del orden en que ocurren dos cosas que nadie ha ordenado. El caso típico son dos procesos que comparten un dato y lo modifican a la vez. Cada uno hace lo que parece una operación, «sumar uno al contador», pero la máquina la ejecuta en tres: leer el valor, sumarle uno, escribir el resultado. Si el segundo proceso lee entre la lectura y la escritura del primero, los dos parten del mismo número, los dos escriben el mismo resultado y uno de los dos incrementos desaparece sin dejar rastro. Nadie ha hecho nada mal y el contador está mal.
La figura de esta página lo reproduce con dos hilos que suman uno a un contador compartido, cuatro veces cada uno. Si el reparto del tiempo entre los dos fuera perfecto el resultado sería ocho, pero el orden de los pasos lo decide el azar, como lo decide en un ordenador de verdad el planificador del sistema operativo, y casi nunca sale ocho. Las escrituras que machacan un valor que otro hilo acababa de escribir se marcan en rojo. Con el cerrojo activado, cada hilo hace sus tres pasos seguidos sin que el otro pueda meterse en medio, y el resultado es siempre ocho; a cambio, los dos hilos ya no van en paralelo de verdad.
Lo que hace a este fallo tan temido es su carácter. No es determinista: el mismo programa con los mismos datos sale bien mil veces y mal la mil una, según cómo haya repartido el procesador esa vez. La ventana en la que puede ocurrir dura a veces nanosegundos, así que las pruebas pasan y el fallo aparece en producción, con carga, cuando los hilos son muchos. Y desaparece cuando se intenta observar, porque añadir un mensaje de depuración cambia los tiempos lo justo para que el entrelazado dañino deje de darse; los programadores lo llaman un heisenbug. Tampoco necesita hilos. Dos personas que editan el mismo registro desde dos pantallas y guardan con segundos de diferencia pierden el cambio de una de ellas, dos transferencias simultáneas pueden dejar una cuenta en negativo, y un programa que comprueba si tiene permiso sobre un fichero y lo abre un instante después puede abrir otro fichero, si alguien cambió el enlace entre la comprobación y el uso.
La solución tiene nombre desde 1965, cuando Edsger Dijkstra planteó el problema de la exclusión mutua: cómo garantizar que, de varios procesos que comparten algo, solo uno esté dentro de su sección crítica en cada momento, sin que ninguno espere para siempre. Lo resolvió con una pura secuencia de lecturas y escrituras, y en seguida con una abstracción más cómoda, el semáforo. De ahí vienen el cerrojo (lock o mutex) de la figura, las instrucciones atómicas que los procesadores incorporan para que «leer, comparar y escribir» sea de verdad un solo paso, y la idea de transacción de las bases de datos relacionales, que es la misma promesa de atomicidad hecha a escala de un disco: o se ve todo el cambio o no se ve nada de él. Robert Netzer y Barton Miller precisaron en 1992 lo que a menudo se mezcla bajo el mismo nombre: una carrera de datos, dos accesos al mismo dato sin orden entre ellos, y una carrera general, en la que lo que falla es que el programa dependa de un orden que no ha fijado.
Los cerrojos resuelven la carrera y abren otros problemas. Serializan: lo que debía ir en paralelo va por turnos, y si el cerrojo protege demasiado, el programa con ocho procesadores corre como con uno. Y pueden quedarse esperándose unos a otros. Si un hilo toma el cerrojo A y pide el B mientras otro ha tomado el B y pide el A, los dos se quedan quietos para siempre, un interbloqueo (deadlock) que Dijkstra ilustró con cinco filósofos sentados a una mesa con cinco tenedores que necesitan dos cada uno para comer. Por eso buena parte de la ingeniería reciente ha preferido no compartir: pasarse mensajes en lugar de tocar la misma memoria, usar datos que no cambian una vez creados, o lenguajes como Rust, cuyo compilador rechaza un programa en el que dos hilos puedan escribir a la vez el mismo dato antes de que llegue a ejecutarse.
Dos accidentes dan la medida del daño. El Therac-25 era un acelerador lineal para radioterapia que entre 1985 y 1987 administró a seis pacientes dosis de radiación cientos de veces superiores a las prescritas; tres murieron. Nancy Leveson y Clark Turner reconstruyeron la investigación y encontraron, entre otros fallos, una carrera entre la tarea que leía lo que tecleaba la operadora y la que colocaba el equipo: si la operadora corregía el modo de tratamiento en menos de ocho segundos, la máquina disparaba el haz de electrones de alta potencia sin la pieza que debía frenarlo. El 14 de agosto de 2003, cincuenta millones de personas se quedaron sin luz en el noreste de Estados Unidos y en Ontario. El informe oficial de 2004 encontró que el sistema de alarmas de la empresa eléctrica de Ohio, el que debía avisar de que las líneas se estaban cayendo una tras otra, se había quedado colgado más de una hora por una condición de carrera en su programa, y que los operadores pasaron ese tiempo sin saber que algo iba mal.
La carrera se llama así porque dos procesos corren hacia el mismo sitio, pero no la gana ninguno. La lección que deja es que, en cuanto hay más de un actor, el orden deja de ser gratis y pasa a ser algo que hay que decidir y hacer cumplir. Un programa correcto con un solo hilo puede dejar de serlo con dos sin cambiar una línea, y lo que lo arregla no es más velocidad sino menos coincidencia.
Transacción (informática) – Atomicidad (informática) – El sistema operativo – Las bases de datos relacionales – Del código al proceso
Fuentes
- Edsger W. DijkstraSolution of a Problem in Concurrent Programming ControlCommunications of the ACM 8 (9)1965enlace
- Robert H. B. Netzer y Barton P. MillerWhat Are Race Conditions? Some Issues and FormalizationsACM Letters on Programming Languages and Systems 1 (1)1992enlace
- Nancy G. Leveson y Clark S. TurnerAn Investigation of the Therac-25 AccidentsComputer 26 (7)1993enlace
- U.S.-Canada Power System Outage Task ForceFinal Report on the August 14, 2003 Blackout in the United States and CanadaDepartamento de Energía de Estados Unidos2004enlace