Параллель. Для (): Обновите переменную за пределами цикла

Я просто заглядываю к новой.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());

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

35
задан Alex Bagnolini 6 May 2010 в 12:05
поделиться

6 ответов

Вы не можете этого сделать. 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 на A Guide to Parallel Programming

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

Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4 - Stephen Toub

69
ответ дан 27 November 2019 в 06:34
поделиться

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

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

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

17
ответ дан 27 November 2019 в 06:34
поделиться

Ваше предположение верно.

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

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

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

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

5
ответ дан 27 November 2019 в 06:34
поделиться

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

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

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

4
ответ дан 27 November 2019 в 06:34
поделиться

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

4
ответ дан 27 November 2019 в 06:34
поделиться

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

long sum1 = 0;
long sum2 = 0;

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

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

-1
ответ дан 27 November 2019 в 06:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: