Parallel.For (): Variável de atualização fora do loop

Eu estou apenas olhando para os novos resources do .NET 4.0. Com isso, estou tentando um cálculo simples usando Parallel.For e um loop normal for(x;x;x) .

No entanto, estou obtendo resultados diferentes em cerca de 50% do tempo.

 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()); 

Meu palpite é que os threads estão tentando atualizar “sum” ao mesmo tempo.
Existe uma maneira óbvia de contornar isso?

Você não pode fazer isso. sum está sendo compartilhada através de seus threads paralelos. Você precisa ter certeza de que a variável sum está sendo acessada apenas por um thread de cada vez:

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

MAS … Este é um antipadrão porque você efetivamente serializou o loop porque cada thread travará no Interlocked.Add .

O que você precisa fazer é adicionar sub-totais e mesclá-los no final desta forma:

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

Você pode encontrar mais discussões sobre isso no MSDN: http://msdn.microsoft.com/en-us/library/dd460703.aspx

PLUG: Você pode encontrar mais sobre isso no capítulo 2 em um guia para programação paralela

O seguinte também é definitivamente vale a pena ler …

Padrões para Programação Paralela: Entendendo e Aplicando Padrões Paralelos com o .NET Framework 4 – Stephen Toub

sum += y; é na verdade sum = sum + y; . Você está obtendo resultados incorretos devido à seguinte condição de corrida:

  1. Thread1 lê a sum
  2. Thread2 lê a sum
  3. Thread1 calcula sum+y1 e armazena o resultado na sum
  4. Thread2 calcula sum+y2 e armazena o resultado na sum

sum é agora igual a sum+y2 , em vez de sum+y1+y2 .

Sua suposição está correta.

Quando você escreve sum += y , o tempo de execução faz o seguinte:

  1. Leia o campo na pilha
  2. Adicione y à pilha
  3. Escreva o resultado de volta ao campo

Se dois threads lerem o campo ao mesmo tempo, a alteração feita pelo primeiro thread será substituída pelo segundo thread.

Você precisa usar o Interlocked.Add , que realiza a adição como uma única operação atômica.

Incrementar um longo não é uma operação atômica.

Eu acho que é importante distinguir que esse loop não é capaz de ser particionado para paralelismo, porque como foi mencionado acima, cada iteração do loop é dependente do anterior. O paralelo for é projetado para tarefas explicitamente paralelas, como escalonamento de pixels, etc., porque cada iteração do loop não pode ter dependencies de dados fora de sua iteração.

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

Acima, um exemplo de código que permite um particionamento fácil para o paralelismo. No entanto, uma palavra de advertência, o paralelismo vem com um custo, até mesmo o loop que usei como exemplo acima é muito simples demais para se preocupar com um paralelo porque o tempo de configuração leva mais tempo do que o tempo economizado via paralelismo.

Um ponto importante que ninguém parece ter mencionado: para operações paralelas de dados (como os OPs), geralmente é melhor (em termos de eficiência e simplicidade) usar o PLINQ em vez da class Parallel . O código do OP é realmente trivial para paralelizar:

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

O snippet acima usa o método ParallelEnumerable.Sum , embora também seja possível usar o Aggregate para cenários mais gerais. Consulte o capítulo Loops paralelos para obter uma explicação dessas abordagens.

se houver dois parâmetros neste código. Por exemplo

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

o que faremos ? Eu estou supondo que tem que usar matriz!