2012-02-06 25 views
7

¿Alguien puede explicar por qué este programa devuelve el valor correcto para sqrt_min?parallel.foreach funciona, pero ¿por qué?

int n = 1000000; 

double[] myArr = new double[n]; 
for(int i = n-1 ; i>= 0; i--){ myArr[i] = (double)i;} 

// sqrt_min contains minimal sqrt-value 
double sqrt_min = double.MaxValue; 

Parallel.ForEach(myArr, num => 
{ 
double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized 
if(sqrt < sqrt_min){ sqrt_min = sqrt;} 
}); 
Console.WriteLine("minimum: "+sqrt_min); 
+1

ver también http://stackoverflow.com/questions/3679209/why-doesnt-this-code-demonstrate-the-non-atomicity-of-reads-writes~~V~~singular~~3rd puede que no sea suerte. Puede ser porque su CPU ofrece operaciones dobles atómicas, aunque C# no lo garantiza. – hatchet

+1

Existe la posibilidad de que corregir este error introduzca suficiente contención de bloqueo que será más lento que simplemente calcular las raíces cuadradas en un hilo. Una solución mejor sería hacer que cada CPU calcule muchas raíces cuadradas, controlando sus propios mínimos y encontrando el mínimo global cuando cada CPU está lista. Aún tendrá contención, pero la afirmación solo se aplicará al comparar los mínimos locales específicos del subproceso, en lugar de cada raíz cuadrada. – Brian

Respuesta

13

Funciona por pura suerte. A veces, cuando lo ejecuta, es lucky que las lecturas y escrituras no atómicas en el doble no están dando como resultado valores "rasgados". A veces usted es afortunado que las pruebas no atómicas y los conjuntos simplemente establecen el valor correcto cuando ocurre esa carrera. No hay garantía de que este programa produzca ningún resultado en particular.

+0

¿cuál es la mejor manera de resolver este problema? – user1193134

+1

@ user1193134: en general, bloqueos o operaciones atómicas (extremadamente cuidadosas). En su caso particular, PLINQ 'Aggregate()' o simplemente 'Min()'. – SLaks

+0

¿podría ayudarme a resolver este problema? – user1193134

5

Tu código no es seguro; solo funciona por coincidencia.

Si dos hilos corren el if simultáneamente, uno de los mínimos se sobrescribirán:

  • sqrt_min = 6
  • Tema A: sqrt = 5
  • Tema B: sqrt = 4
  • Tema A entra en el if
  • El hilo B ingresa al if
  • Tema B asigna sqrt_min = 4
  • Tema Un asigna sqrt_min = 5

En los sistemas de 32 bits, también es vulnerable a la lectura/escritura de rasgado.

Se podría hacer esto seguro usando Interlocked.CompareExchange en un bucle.

+0

¡eso es lo que pensé! ¡Pero nunca llegué a este punto! Incluso tratando mucho y muchas veces> 10^9 – user1193134

+0

@ user1193134: El tamaño de la matriz realmente no importa (a menos que tenga cientos de núcleos). Usando la modificación de dasblinkenlight, obtengo fallas consistentes. – SLaks

+0

pareces muy familiarizado con lo de Interlocked.CompareExchange. ¿Puedes ayudarme con el problema? – user1193134

3

Su código no funciona muy bien: me encontré en un bucle de 100.000 veces, y falló una vez en mi equipo de 8 núcleos, produciendo esta salida:

minimum: 1 

que acorta las carreras a que el error aparecer más rápido.

Éstos son mis modificaciones:

static void Run() { 
    int n = 10; 

    double[] myArr = new double[n]; 
    for (int i = n - 1; i >= 0; i--) { myArr[i] = (double)i*i; } 

    // sqrt_min contains minimal sqrt-value 
    double sqrt_min = double.MaxValue; 

    Parallel.ForEach(myArr, num => { 
     double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized 
     if (sqrt < sqrt_min) { sqrt_min = sqrt; } 
    }); 
    if (sqrt_min > 0) { 
     Console.WriteLine("minimum: " + sqrt_min); 
    } 
} 


static void Main() { 
    for (int i = 0; i != 100000; i++) { 
     Run(); 
    } 
} 

Esto no es una coincidencia, teniendo en cuenta la falta de sincronización en torno a la lectura y escritura de una variable compartida.

+0

También recibí un error. – SLaks

2

Como han dicho otros, esto solo funciona por falta de cizalladura. Sin embargo, tanto OP como otros carteles han tenido problemas creando la condición de carrera. Eso es bastante fácil de explicar. El código genera muchas condiciones de carrera, pero la gran mayoría de ellos (99.9999% para ser exactos) son irrelevantes. Todo lo que importa al final del día es el hecho de que 0 debería ser el resultado mínimo. Si su código cree que la raíz 5 es mayor que la raíz 6, o que la raíz 234 es mayor que la raíz 235, aún así no se romperá. Tiene que haber una condición de carrera específicamente con la iteración que genera 0. Las probabilidades de que una de las iteraciones tenga una condición de carrera con otra es muy, muy alta. La probabilidad de que la iteración que procesa el último elemento tenga una condición de carrera es realmente bastante baja.

4

Por qué su código original está roto verifique las otras respuestas, no voy a repetir eso.

El subprocesamiento múltiple es más fácil cuando no hay acceso de escritura al estado compartido. Afortunadamente, su código se puede escribir de esa manera. Parallel linq puede ser agradable en tales situaciones, pero a veces la sobrecarga es demasiado grande.

puede volver a escribir el código para:

double sqrt_min = myArr.AsParallel().Select(x=>Math.Sqrt(x)).Min(); 

En su problema específico que es más rápido que cambiar todo el funcionamiento y la MinSqrt, lo cual es posible porque Sqrt está aumentando de forma monótona.

double sqrt_min = Math.Sqrt(myArr.AsParallel().Min()) 
Cuestiones relacionadas