Parallel.For (): обновить переменную вне цикла - PullRequest
35 голосов
/ 05 мая 2010

Я просто смотрю на новые функции .NET 4.0. После этого я пытаюсь выполнить простой расчет с использованием Parallel.For и обычного цикла for(x;x;x).

Однако в 50% случаев я получаю разные результаты.

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

Я предполагаю, что потоки пытаются обновить "сумму" одновременно.
Есть ли очевидный способ обойти это?

Ответы [ 7 ]

69 голосов
/ 05 мая 2010

Вы не можете сделать это. sum распределяется между вашими параллельными потоками. Вы должны убедиться, что переменная sum доступна только одному потоку за раз:

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

НО ... Это анти-паттерн, потому что вы эффективно сериализовали цикл, потому что каждый поток блокируется на Interlocked.Add.

Что вам нужно сделать, это добавить промежуточные итоги и объединить их в конце, например:

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

Вы можете найти дальнейшее обсуждение этого на MSDN: http://msdn.microsoft.com/en-us/library/dd460703.aspx

PLUG: Подробнее об этом вы можете прочитать в Главе 2 Руководство по параллельному программированию

Следующее также определенно стоит прочитать ...

Шаблоны для параллельного программирования: понимание и применение параллельных шаблонов в .NET Framework 4 - Стивен Тауб

17 голосов
/ 05 мая 2010

sum += y; на самом деле sum = sum + y;. Вы получаете неверные результаты из-за следующих условий гонки:

  1. Тема 1 читает sum
  2. Тема 2 читает sum
  3. Thread1 вычисляет sum+y1 и сохраняет результат в sum
  4. Thread2 вычисляет sum+y2 и сохраняет результат в sum

sum теперь равно sum+y2 вместо sum+y1+y2.

5 голосов
/ 05 мая 2010

Ваша догадка верна.

Когда вы пишете sum += y, среда выполнения делает следующее:

  1. Считать поле в стек
  2. Добавить y в стек
  3. Запишите результат обратно в поле

Если два потока читают поле одновременно, изменения, внесенные первым потоком, будут перезаписаны вторым потоком.

Вам необходимо использовать Interlocked.Add, который выполняет сложение как одну атомарную операцию.

4 голосов
/ 05 мая 2010

Я думаю, что важно различать, что этот цикл не может быть разделен для параллелизма, потому что, как было упомянуто выше, каждая итерация цикла зависит от предыдущего. Параллель for предназначена для явно параллельных задач, таких как масштабирование пикселей и т. Д., Поскольку каждая итерация цикла не может иметь зависимости данных вне своей итерации.

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

Приведенный выше пример кода, который позволяет легко разделить для параллелизма. Тем не менее, предупреждающее слово: параллелизм обходится дорого, даже цикл, который я использовал в качестве примера выше, слишком прост, чтобы возиться с параллелью, потому что время установки занимает больше времени, чем время, сэкономленное благодаря параллелизму.

4 голосов
/ 05 мая 2010

Увеличение long не является атомарной операцией.

3 голосов
/ 15 июня 2013

Важный момент, который, кажется, никто не упомянул: для операций с параллельными данными (таких как OP) часто лучше (с точки зрения как эффективности, так и простоты) использовать PLINQ вместо класса Parallel.Код ОП на самом деле тривиален для распараллеливания:

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

В приведенном выше фрагменте кода используется метод ParallelEnumerable.Sum, хотя можно также использовать Aggregate для более подробной информации.общие сценарии.Обратитесь к главе Parallel Loops для объяснения этих подходов.

0 голосов
/ 25 мая 2010

, если в этом коде два параметра. Например

long sum1 = 0;
long sum2 = 0;

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

что мы будем делать? Я предполагаю, что должен использовать массив!

...