¿Qué es una condición de carrera?

Resuelto bmurphy1976 asked hace 16 años • 19 respuestas

Al escribir aplicaciones multiproceso, uno de los problemas más comunes que se experimentan son las condiciones de carrera.

Mis preguntas a la comunidad son:

  • ¿Cuál es la condición de carrera?
  • ¿Cómo los detectas?
  • ¿Cómo los manejas?
  • Finalmente, ¿cómo se evita que ocurran?
bmurphy1976 avatar Aug 29 '08 22:08 bmurphy1976
Aceptado

Una condición de carrera ocurre cuando dos o más subprocesos pueden acceder a datos compartidos e intentan cambiarlos al mismo tiempo. Debido a que el algoritmo de programación de subprocesos puede intercambiar entre subprocesos en cualquier momento, no se sabe el orden en el que los subprocesos intentarán acceder a los datos compartidos. Por lo tanto, el resultado del cambio en los datos depende del algoritmo de programación de subprocesos, es decir, ambos subprocesos "compiten" para acceder/cambiar los datos.

Los problemas a menudo ocurren cuando un hilo hace una "verificación y luego actúa" (por ejemplo, "verificar" si el valor es X, luego "actuar" para hacer algo que depende de que el valor sea X) y otro hilo hace algo con el valor en entre el "cheque" y el "acto". P.ej:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

El punto es que y podría ser 10, o podría ser cualquier cosa, dependiendo de si otro hilo cambió x entre la verificación y la acción. No tienes forma real de saberlo.

Para evitar que se produzcan condiciones de carrera, normalmente se bloquearían los datos compartidos para garantizar que solo un subproceso pueda acceder a los datos a la vez. Esto significaría algo como esto:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x
Lehane avatar Aug 29 '2008 16:08 Lehane

Existe una "condición de carrera" cuando el código multiproceso (o paralelo) que accedería a un recurso compartido podría hacerlo de tal manera que provoque resultados inesperados.

Tome este ejemplo:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

Si tuviera 5 subprocesos ejecutando este código a la vez, el valor de x NO terminaría siendo 50.000.000. De hecho, variaría con cada ejecución.

Esto se debe a que, para que cada hilo incremente el valor de x, tiene que hacer lo siguiente: (simplificado, obviamente)

Recuperar el valor de x
Suma 1 a este valor
Almacene este valor en x

Cualquier hilo puede estar en cualquier paso de este proceso en cualquier momento y pueden pisarse entre sí cuando se trata de un recurso compartido. El estado de x puede ser cambiado por otro hilo durante el tiempo entre la lectura de x y el momento en que se vuelve a escribir.

Digamos que un hilo recupera el valor de x, pero aún no lo ha almacenado. Otro hilo también puede recuperar el mismo valor de x (porque ningún hilo lo ha cambiado todavía) y entonces ambos estarían almacenando el mismo valor (x+1) nuevamente en x.

Ejemplo:

Hilo 1: lee x, el valor es 7
Hilo 1: suma 1 a x, el valor ahora es 8
Hilo 2: lee x, el valor es 7
Hilo 1: almacena 8 en x
Hilo 2: suma 1 a x, el valor ahora es 8
Hilo 2: almacena 8 en x

Las condiciones de carrera se pueden evitar empleando algún tipo de mecanismo de bloqueo antes del código que accede al recurso compartido:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

Aquí, la respuesta siempre es 50.000.000.

Para obtener más información sobre el bloqueo, busque: exclusión mutua, semáforo, sección crítica, recurso compartido.

privatehuff avatar Aug 29 '2008 17:08 privatehuff