2012-02-06 2 views
7

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

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); 
+1

смотри также http://stackoverflow.com/questions/3679209/why-doesnt-this-code-demonstrate-the-non-atomicity-of-reads-writes это не может быть удача. Это может быть связано с тем, что ваш процессор обеспечивает атомарные двойные операции, хотя C# не гарантирует его. – hatchet

+1

Существует вероятность того, что исправление этой ошибки приведет к достаточно большому количеству конфликтов, которые будут медленнее, чем просто вычисление квадратных корней на одном потоке. Лучшим решением было бы, чтобы каждый процессор вычислял много квадратных корней, отслеживая их собственные минимумы и находил глобальный минимум, когда каждый процессор был выполнен. У вас все еще есть конкуренция, но конкуренция будет применяться только при сравнении локальных минимумов, специфичных для потока, а не для каждого квадратного корня. – Brian

ответ

13

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

+0

так что это лучший способ решить эту проблему? – user1193134

+1

@ user1193134: В общем, блокировки или (чрезвычайно осторожные) атомные операции. В вашем конкретном случае PLINQ 'Aggregate()' или просто 'Min()'. – SLaks

+0

Не могли бы вы дать мне руку на решение этой проблемы? – user1193134

5

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

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

  • sqrt_min = 6
  • Автор А: sqrt = 5
  • резьбы Б: sqrt = 4
  • Поток А входит в if
  • Резьба B входит в if
  • Thread B назначает sqrt_min = 4
  • Поток А назначает sqrt_min = 5

На 32-битных системах, вы также уязвимы для чтения/записи слезотечение.

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

+0

вот что я подумал! Но я до сих пор не дошел до этого! Даже при попытке очень много и много раз> 10^9 – user1193134

+0

@ user1193134: Размер массива не имеет значения (если у вас нет сотен ядер). Используя модификацию dasblinkenlight, я получаю последовательные сбои. – SLaks

+0

Вы, кажется, очень знакомы с Interlocked.CompareExchange. Можете ли вы дать мне руку с проблемой? – user1193134

3

Ваш код действительно не работает: я запустил его в петлю 100000 раз, и он не раз на моем 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(); 
    } 
} 

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

+0

У меня тоже был провал. – SLaks

2

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

4

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

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

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

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

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

double sqrt_min = Math.Sqrt(myArr.AsParallel().Min()) 
Смежные вопросы