Parallel.For (): actualiza la variable fuera del ciclo

Solo estoy viendo las nuevas características de .NET 4.0. Con eso, estoy intentando hacer un cálculo simple usando Parallel.For y un ciclo normal for(x;x;x) .

Sin embargo, obtengo resultados diferentes aproximadamente el 50% del tiempo.

 long sum = 0; Parallel.For(1, 10000, y => { sum += y; } ); Console.WriteLine(sum.ToString()); sum = 0; for (int y = 1; y < 10000; y++) { sum += y; } Console.WriteLine(sum.ToString()); 

Supongo que los hilos intentan actualizar “sum” al mismo tiempo.
¿Hay una forma obvia de evitarlo?

No puedes hacer esto. sum se está compartiendo a través de sus hilos paralelos. Debes asegurarte de que solo se accede a la variable de sum por un hilo a la vez:

 // DON'T DO THIS! Parallel.For(0, data.Count, i => { Interlocked.Add(ref sum, data[i]); }); 

PERO … Esto es un antipatrón porque efectivamente serializaste el bucle porque cada hilo se bloqueará en el Interlocked.Add .

Lo que debe hacer es agregar sub totales y fusionarlos al final de esta manera:

 Parallel.For(0, result.Count, () => 0, (i, loop, subtotal) => { subtotal += result[i]; return subtotal; }, (x) => Interlocked.Add(ref sum, x) ); 

Puede encontrar más información al respecto en MSDN: http://msdn.microsoft.com/en-us/library/dd460703.aspx

ENCHUFE: Puede encontrar más sobre esto en el Capítulo 2 de Una guía para progtwigción en paralelo

Lo siguiente definitivamente también vale la pena leer …

Patrones para la progtwigción paralela: comprensión y aplicación de patrones paralelos con .NET Framework 4 – Stephen Toub

sum += y; es en realidad sum = sum + y; . Está obteniendo resultados incorrectos debido a la siguiente condición de carrera:

  1. Thread1 lee sum
  2. Thread2 lee sum
  3. Thread1 calcula sum+y1 , y almacena el resultado en sum
  4. Thread2 calcula sum+y2 , y almacena el resultado en sum

sum ahora es igual a sum+y2 , en lugar de sum+y1+y2 .

Su suposición es correcta.

Cuando escribe sum += y , el tiempo de ejecución hace lo siguiente:

  1. Lee el campo en la stack
  2. Agrega y a la stack
  3. Escribe el resultado de vuelta al campo

Si dos hilos leen el campo al mismo tiempo, el segundo hilo sobrescribirá el cambio realizado por el primer hilo.

Interlocked.Add usar Interlocked.Add , que realiza la adición como una única operación atómica.

Incrementar un largo no es una operación atómica.

Creo que es importante distinguir que este bucle no puede ser particionado para el paralelismo, porque como se mencionó anteriormente, cada iteración del bucle depende del previo. El paralelo para está diseñado para tareas explícitamente paralelas, como el escalado de píxeles, etc. porque cada iteración del ciclo no puede tener dependencias de datos fuera de su iteración.

 Parallel.For(0, input.length, x => { output[x] = input[x] * scalingFactor; }); 

Lo anterior es un ejemplo de código que permite una partición fácil para el paralelismo. Sin embargo, una palabra de advertencia, el paralelismo viene con un costo, incluso el ciclo que utilicé como ejemplo anterior es demasiado fácil de molestar con un paralelo porque el tiempo de configuración lleva más tiempo que el tiempo ahorrado mediante el paralelismo.

Un punto importante que nadie parece haber mencionado: para las operaciones paralelas a los datos (como los OP), a menudo es mejor (en términos de eficiencia y simplicidad) usar PLINQ en lugar de la clase Parallel . El código de OP es realmente trivial para paralelizar:

 long sum = Enumerable.Range(1, 10000).AsParallel().Sum(); 

El fragmento de arriba usa el método ParallelEnumerable.Sum , aunque también se puede usar Aggregate para escenarios más generales. Consulte el capítulo Parallel Loops para obtener una explicación de estos enfoques.

si hay dos parámetros en este código. Por ejemplo

 long sum1 = 0; long sum2 = 0; Parallel.For(1, 10000, y => { sum1 += y; sum2=sum1*y; } ); 

que haremos ? ¡Adivino que tengo que usar array!