Parallel.For (): Aggiorna variabile fuori dal ciclo

Sto solo cercando le nuove funzionalità di .NET 4.0. Con ciò, sto tentando un semplice calcolo usando Parallel.For e un normale for(x;x;x) ciclo for(x;x;x) .

Tuttavia, sto ottenendo risultati diversi circa il 50% delle volte.

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

La mia ipotesi è che i thread stanno cercando di aggiornare “sum” allo stesso tempo.
C’è un modo ovvio intorno ad esso?

Non puoi farlo. sum viene condivisa tra i thread paralleli. Devi assicurarti che la variabile sum sia accessibile solo da un thread alla volta:

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

MA … Questo è un anti-pattern perché hai effettivamente serializzato il ciclo perché ogni thread si bloccherà su Interlocked.Add .

Quello che devi fare è aggiungere i totali parziali e unirli alla fine in questo modo:

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

È ansible trovare ulteriori discussioni su questo MSDN: http://msdn.microsoft.com/en-us/library/dd460703.aspx

SPINA: Puoi trovare ulteriori informazioni al riguardo nel Capitolo 2 su Guida alla programmazione parallela

Quanto segue merita sicuramente una lettura …

Modelli per la programmazione parallela: comprensione e applicazione di pattern paralleli con .NET Framework 4 – Stephen Toub

sum += y; è in realtà sum = sum + y; . Stai ottenendo risultati errati a causa delle seguenti condizioni di gara:

  1. Thread1 legge la sum
  2. Thread2 legge la sum
  3. Thread1 calcola sum+y1 e memorizza il risultato in sum
  4. Thread2 calcola sum+y2 e memorizza il risultato in sum

sum ora è uguale alla sum+y2 , invece della sum+y1+y2 .

Il tuo supporre è corretto.

Quando scrivi sum += y , il runtime esegue quanto segue:

  1. Leggi il campo in pila
  2. Aggiungi y alla pila
  3. Scrivi il risultato sul campo

Se due thread leggono il campo allo stesso tempo, la modifica apportata dal primo thread verrà sovrascritta dal secondo thread.

È necessario utilizzare Interlocked.Add , che esegue l’aggiunta come una singola operazione atomica.

Incrementare a lungo non è un’operazione atomica.

Penso che sia importante distinguere che questo ciclo non è in grado di essere partizionato per il parallelismo, perché come è stato menzionato sopra ogni iterazione del ciclo dipende dal precedente. Il parallelo per è progettato per attività esplicitamente parallele, come il ridimensionamento dei pixel ecc. Perché ogni iterazione del ciclo non può avere dipendenze di dati al di fuori della sua iterazione.

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

Quanto sopra un esempio di codice che consente un facile partizionamento per il parallelismo. Tuttavia, una parola di avvertimento, il parallelismo ha un costo, anche il ciclo che ho usato come esempio sopra è troppo semplice per preoccuparsi di un parallelo perché il tempo di impostazione richiede più tempo rispetto al tempo risparmiato tramite il parallelismo.

Un punto importante che nessuno sembra aver menzionato: per le operazioni in parallelo dati (come gli OP), spesso è meglio (in termini di efficienza e semplicità) utilizzare PLINQ invece della class Parallel . Il codice dell’OP è in realtà banale da parallelizzare:

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

Il frammento sopra riportato utilizza il metodo ParallelEnumerable.Sum , sebbene sia ansible utilizzare Aggregate per scenari più generali. Fare riferimento al capitolo Cicli paralleli per una spiegazione di questi approcci.

se ci sono due parametri in questo codice. Per esempio

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

Che cosa faremo ? Sto indovinando che devono usare la matrice!