Параллельно. работает, но почему? - PullRequest
7 голосов
/ 06 февраля 2012

Может кто-нибудь объяснить, почему эта программа возвращает правильное значение для sqrt_min?

int n = 1000000;

double[] myArr = new double[n];
for(int i = n-1 ; i>= 0; i--){ myArr[i] = (double)i;}

// sqrt_min contains minimal sqrt-value
double sqrt_min = double.MaxValue;

Parallel.ForEach(myArr, num =>
{
double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized
if(sqrt < sqrt_min){ sqrt_min = sqrt;}
});
Console.WriteLine("minimum: "+sqrt_min);

Ответы [ 5 ]

13 голосов
/ 06 февраля 2012

Это работает по счастливой случайности. Иногда, когда вы запускаете его, вам везет , что неатомарные операции чтения и записи в double не приводят к "порванным" значениям. Иногда вам везет , что неатомарные тесты и наборы просто устанавливают правильное значение, когда происходит эта гонка. Нет никаких гарантий, что эта программа даст какой-либо конкретный результат.

5 голосов
/ 06 февраля 2012

Ваш код не является безопасным;это работает только по стечению обстоятельств.

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

  • sqrt_min = 6
  • Поток A: sqrt = 5
  • нить B: sqrt = 4
  • нить A входит в if
  • нить B входит в if
  • нить B присваивает sqrt_min = 4
  • поток A назначает sqrt_min = 5

В 32-разрядных системах вы также подвержены разрыву чтения / записи.

Можно было бы сделать это безопаснымиспользуя Interlocked.CompareExchange в цикле.

4 голосов
/ 07 февраля 2012

Почему ваш исходный код не работает, проверьте другие ответы, я не буду повторять это.

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

Вы можете переписать свой код на:

double sqrt_min = myArr.AsParallel().Select(x=>Math.Sqrt(x)).Min();

В вашей конкретной проблеме быстрее поменять местами операции Min и Sqrt, что возможно потому, что Sqrt монотонно увеличивается.

double sqrt_min = Math.Sqrt(myArr.AsParallel().Min())
3 голосов
/ 06 февраля 2012

Ваш код на самом деле не работает: я запустил его 100 000 раз в цикле, и на моем 8-ядерном компьютере он один раз завершился с ошибкой:

minimum: 1

Я сократил прогоны, чтобы ошибка выглядела быстрее.

Вот мои модификации:

static void Run() {
    int n = 10;

    double[] myArr = new double[n];
    for (int i = n - 1; i >= 0; i--) { myArr[i] = (double)i*i; }

    // sqrt_min contains minimal sqrt-value
    double sqrt_min = double.MaxValue;

    Parallel.ForEach(myArr, num => {
        double sqrt = Math.Sqrt(num); // some time consuming calculation that should be parallized
        if (sqrt < sqrt_min) { sqrt_min = sqrt; }
    });
    if (sqrt_min > 0) {
        Console.WriteLine("minimum: " + sqrt_min);
    }
}


static void Main() {
    for (int i = 0; i != 100000; i++ ) {
        Run();
    }
}

Это не совпадение, учитывая отсутствие синхронизации чтения и записи общей переменной.

2 голосов
/ 07 февраля 2012

Как уже говорили другие, это работает только на основе удачи сдвига.И у ОП, и у других постеров возникли проблемы с созданием условий гонки.Это довольно легко объяснить.Код генерирует множество состояний гонки, но подавляющее большинство из них (точнее 99,9999%) не имеют значения.Все, что имеет значение в конце дня, - факт, что 0 должен быть минимальным результатом.Если ваш код думает, что root 5 больше root 6, или что root 234 больше root 235, он все равно не сломается.Должно быть условие гонки, особенно с итерацией, генерирующей 0. Шансы, что одна из итераций имеет условие гонки с другой, очень и очень высоки.Вероятность того, что итерация, обрабатывающая последний элемент, имеет условие гонки, действительно довольно низкая.

...