Использование вложенного Parallel.For - PullRequest
6 голосов
/ 19 июля 2010

Рассмотрим этот пример:

var x = 0;

for (var i = 0; i < 100; i++ )
{
    for (var a = i+1; a < 100; a++)
        x += 1;
}

При печати x мы всегда получаем 4950. А что, если я хочу распараллелить это?

Это то, что я придумал

Parallel.For(0, 100, i => Parallel.For(i + 1, 100, a => { x += 1; }));

Однако это не печатает 4950 каждый раз, когда я запускаю его. Почему?

Ответы [ 3 ]

13 голосов
/ 19 июля 2010

Parallel Extensions помогает вам создавать, распределять, запускать и выполнять задачи в почти обязательном синтаксисе.То, что не делает, это заботится о всех видах безопасности потоков (одна из ловушек ).Вы пытаетесь заставить параллельные потоки одновременно обновлять одну общую переменную.Чтобы сделать что-то подобное правильно, вы должны ввести, например, блокировку.

Я не уверен, что ты пытаешься сделать.Я предполагаю, что ваш код - просто заполнитель или эксперимент.Распараллеливание подходит только тогда, когда вы можете изолировать ваши различные части работы;не тогда, когда вам постоянно приходится встречаться с общими данными.

2 голосов
/ 04 октября 2013

Здесь был бы «правильный» способ сделать это, это не потребовало бы от вас блокировки вашего конечного итогового объекта и потребовало бы только выполнения блокированных операций в конце цикла каждого локального потока.

int x = 0;
Parallel.For(0, 100,
    () => 0, //LocalInit
    (i, loopstate, outerlocal) =>
    {
        Parallel.For(i + 1, 100,
            () => 0, //LocalInit
            (a, loopState, innerLocal) => { return innerLocal + 1; },
            (innerLocal) => Interlocked.Add(ref outerlocal, innerLocal)); //Local Final
        return outerlocal;
    },
    (outerLocal) => Interlocked.Add(ref x, outerLocal)); //Local Final

Однако иметь два вложенных оператора Parallel, выполняющих эту небольшую работу, - плохая идея. Есть накладные расходы, которые необходимо учитывать, если вы выполняете такой небольшой объем работы, было бы гораздо лучше сделать только одно заявление Parallel или вообще не выполнять его.

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

0 голосов
/ 19 июля 2010

Как альтернатива блокировке каждый раз, вы можете использовать локальные переменные потока в сочетании с блокировкой:

Object thisLock = new Object();
var globalSum = 0;
System.Threading.Tasks.Parallel.For(0, 100, i => {
    System.Threading.Tasks.Parallel.For(i + 1, 100, () => 0, (num, loopState, subSum) => ++subSum, subSum => { 
        lock(thisLock) { globalSum += subSum; }
    });
});

Console.WriteLine(globalSum);
...