2012-02-06 19 views
7

Czy ktoś może wyjaśnić, dlaczego ten program zwraca poprawną wartość dla sqrt_min?parallel.foreach działa, ale dlaczego?

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

zobaczyć również http://stackoverflow.com/questions/3679209/why-doesnt-this-code-demonstrate-the-non-atomicity-of-reads-writes nie może być szczęście. Być może twój procesor dostarcza podwójne operacje atomowe, mimo że C# nie gwarantuje tego. – hatchet

+1

Istnieje możliwość, że naprawienie tego błędu wprowadzi dostateczną liczbę twierdzeń, że będzie wolniejsze niż obliczanie pierwiastków kwadratowych w jednym wątku. Lepszym rozwiązaniem byłoby, gdyby każdy procesor obliczał wiele pierwiastków kwadratowych, śledził ich własne minimum i znajdował globalne minimum, kiedy każdy procesor jest wykonywany. Nadal będziesz mieć spór, ale rywalizacja będzie stosowana tylko podczas porównywania lokalnych minimalnych wątków, a nie dla każdego pierwiastka kwadratowego. – Brian

Odpowiedz

13

Działa na szczęście. Czasami, gdy go uruchamiasz, masz szczęście, które nie-atomowe czyta i zapisuje do podwójnego nie powodują "podartych" wartości. Czasami jesteś szczęśliwy, że nie-atomowe testy i zestawy właśnie ustawiają prawidłową wartość, gdy wyścig się dzieje. Nie ma gwarancji, że program ten przyniesie określony rezultat.

+0

jaki jest najlepszy sposób rozwiązania tego problemu? – user1193134

+1

@ user1193134: Zasadniczo blokuje lub (wyjątkowo ostrożnie) operacje atomowe. W twoim konkretnym przypadku, PLINQ 'Aggregate()' lub po prostu 'Min()'. – SLaks

+0

czy mógłbyś mi pomóc w rozwiązaniu tego problemu? – user1193134

5

Twój kod nie jest bezpieczny; działa tylko przez przypadek.

Jeśli dwa wątki uruchomić if jednocześnie jednym z minimów zostaną nadpisane:

  • sqrt_min = 6
  • Temat A: sqrt = 5
  • Temat B: sqrt = 4
  • nawlec wchodzi if
  • Gwint B wchodzi do if
  • Wątek B przypisuje sqrt_min = 4
  • nawlec przypisuje sqrt_min = 5

W systemach 32-bitowych, jesteś również podatne na zapis/odczyt łzawienie.

Byłoby możliwe, aby było to bezpieczne przy użyciu pętli Interlocked.CompareExchange.

+0

to jest to, co myślałem! Ale nigdy nie dotarłem do tego punktu! Nawet próbując bardzo mocno i całkiem sporo razy> 10^9 – user1193134

+0

@ user1193134: Rozmiar tablicy nie ma większego znaczenia (chyba, że ​​masz setki rdzeni). Przy użyciu modyfikacji dasblinkenlight uzyskuję spójne błędy. – SLaks

+0

wydaje Ci się, że znasz Interlocked.CompareExchange. Czy możesz mi pomóc w rozwiązaniu problemu? – user1193134

3

Twój kod nie naprawdę działa: Pobiegłem go w pętli 100.000 razy, a on nie raz na moim 8-rdzeniowego komputera, tworząc to wyjście:

minimum: 1 

ja skróciła biegnie do błędu wydają się szybsze.

Oto moje modyfikacje:

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

To nie jest zbieg okoliczności, biorąc pod uwagę brak synchronizacji wokół czytania i pisania zmiennej współdzielonej.

+0

Mam też awarię. – SLaks

2

Jak powiedzieli inni, działa to tylko na podstawie siły ścinającej. Zarówno OP, jak i inne plakaty miały trudności z faktycznym stworzeniem warunków wyścigu. To dość łatwo wyjaśnić. Kodeks generuje wiele warunków wyścigowych, ale zdecydowana większość z nich (dokładnie 99,9999%) nie ma znaczenia. Wszystko, co ma znaczenie na koniec dnia, to fakt, że 0 powinno być wynikiem minimalnym. Jeśli twój kod uważa, że ​​root 5 jest większy niż root 6, lub że root 234 jest większy niż root 235 to nadal się nie zepsuje. Musi występować warunek wyścigu, szczególnie w przypadku generowania iteracji 0. Szanse, że jedna z iteracji ma stan wyścigowy z drugą, są bardzo, bardzo wysokie. Szanse na to, że iteracja przetwarzająca ostatni element ma stan wyścigowy, są naprawdę niskie.

4

Po to, dlaczego oryginalny kod jest uszkodzony, sprawdź inne odpowiedzi, nie powtórzę tego.

Wielowątkowość jest najłatwiejsza, gdy nie ma dostępu do zapisu do stanu współdzielonego. Na szczęście twój kod można zapisać w ten sposób. Równoległy linq może być miły w takich sytuacjach, ale czasami narzut jest zbyt duży.

można przepisać kod do:

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

W konkretnego problemu to szybciej, aby zamienić się wokół i eksploatacji MinSqrt, co jest możliwe, ponieważ Sqrt jest monotonicznie rosnącą.

double sqrt_min = Math.Sqrt(myArr.AsParallel().Min())