2011-08-03 12 views
12

Wiadomo, że odwołanie pobiera 4 bajty pamięci w 32-bitowym procesorze i 8 bajtach - w 64-bitowym procesorze. Tak więc procesory gwarantują, że pojedyncze odczyty i zapisywane do pamięci w przyrostach naturalnej wielkości słowa maszyny będą przeprowadzane atomowo. Z drugiej strony istnieją 2 metody zblokowane klasy:Użycie Interlocked.Exchange dla aktualizacji referencji i Int32

public static int Exchange(
    ref int location1, 
    int value 
) 

i

public static T Exchange<T>(
    ref T location1, 
    T value 
) 
where T : class 

Więc pytanie brzmi, dlaczego Interlocked.Exchange jest potrzebne do Int32 i dla typów referencyjnych? Nie można tego zrobić bezpiecznie, po prostu za pomocą prostego zadania, ponieważ jest atomowy?

Odpowiedz

9

Nie chodzi tylko o atomowość. Chodzi również o widoczność pamięci. Zmienna może być przechowywana w pamięci głównej lub pamięci podręcznej procesora. Jeśli zmienna jest przechowywana tylko w pamięci podręcznej procesora, nie będzie widoczna dla wątków działających na różnych procesorach. Rozważmy następujący przykład:

public class Test { 
    private Int32 i = 5; 

    public void ChangeUsingAssignment() { 
     i = 10; 
    } 

    public void ChangeUsingInterlocked() { 
     Interlocked.Exchange(ref i, 10); 
    } 

    public Int32 Read() { 
     return Interlocked.CompareExchange(ref i, 0, 0); 
    } 
} 

Teraz, jeśli nazywamy „ChangeUsingAssignment” na jednej nici i „czytać” w innym wątku zwracana wartość może być 5, a nie 10. Ale jeśli zadzwonisz ChangeUsingInterlocked „Czytaj” powróci 10 zgodnie z oczekiwaniami.

----------   ------------   ------------------- 
| CPU 1 | --> | CACHE 1 | --> |     | 
----------   ------------  |     | 
             |  RAM  | 
----------   ------------  |     | 
| CPU 2 | --> | CACHE 2 | --> |     | 
----------   ------------   ------------------- 

Na wykresie powyżej metoda „ChangeUsingAssignement” może spowodować wartość 10 uzyskać „zatrzymany” w pamięci podręcznej 2 i nie uczynić go do pamięci RAM. Kiedy CPU 1 później spróbuje go odczytać, otrzyma wartość z pamięci RAM, w której jest nadal. 5. Używając opcji Interlocked zamiast zwykłego zapisu, upewnij się, że wartość 10 dostaje się do pamięci RAM.

+0

Dziękuję bardzo. Teraz jest to wystarczająco jasne. –

+3

Wiem, że to oczywiście rok później, ale jeśli to możliwe, czy mógłbyś to sprawdzić? Ta strona http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/ wydaje się sugerować, że wszystkie zapisy w języku C# są już niestabilne. – user981225

5

Wymiana wartości pamięci i zawartości rejestru procesora zasadniczo nie jest atomowa. Musisz przeczytać i zapisać lokalizację pamięci. Ponadto, metody Interlocked gwarantują, że operacja jest atomowa nawet na komputerach wielordzeniowych, gdzie każdy rdzeń ma własną pamięć podręczną i potencjalnie własny widok głównej pamięci.

+0

Ale jeśli nie potrzebuję starej wartości i chcę po prostu przypisać nową wartość do jakiejś zmiennej. To tylko pisanie i atomizacja, prawda? –

+0

@OlegDudnyk Myślę, że to nie jest poprawne, nawet jeśli nie zależy Ci na oryginalnej wartości. Zastanów się, jak osiągnąć to w języku asemblerowym, zależnie od tego, gdzie znajduje się twoja zmienna/wartość src i dest, można je przetłumaczyć na więcej niż jedną instrukcję, między dwiema instrukcjami, których aplikacja wciąż może zostać przerwana. W niektórych architekturach można zablokować magistralę, aby upewnić się, że nikt nie jest w stanie odczytać pamięci zapisu (np. IA ma prefiks LOCK), a niektóre instrukcje są gwarantowane atomowe (na przykład XCHG, CMPXCHG itd. Na IA). – codewarrior

8

ma wartość zwracaną, pozwalającą dowiedzieć się, jaką wartość właśnie zastąpiono. Jest to połączenie ustawienia nowej wartości i uzyskując starą wartość, którą te metody osiągają.

+0

Więc jeśli nie potrzebujesz starej wartości, którą możesz po prostu przypisać, ale wtedy potrzebujesz również wywołania Bariery, aby unieważnić pamięć podręczną (cache). –

+0

@Henk: Czy mógłbyś podać więcej szczegółów, co masz na myśli przez połączenie Barier? –

+0

Klasyczne odniesienie: http: //www.albahari.pl/threading/ –

4

Interlock.Exchange zwraca oryginalną wartość podczas wykonywania operacji atomowej. Cały problem polega na zapewnieniu mechanizmu blokującego. Tak więc w rzeczywistości dwie operacje: odczyt oryginalnej wartości i ustawiają nową wartość. Te dwa elementy nie są atomowe.

+1

Ale jeśli nie potrzebuję starej wartości i chcę po prostu przypisać nową wartość do jakiejś zmiennej. To tylko pisanie i atomizacja, prawda? –

+0

@gorik: Jeśli jest to operacja odczytu lub zapisu dla zmiennej dowolnego typu referencyjnego lub dowolnego wbudowanego typu wartości, który zajmuje cztery bajty lub mniej (dla maszyny 32-bitowej), to tak, ma pewność, że jest atomowy . Sprawdź tę serię postów Erica Lipperta na ten temat, będą one pomocne: [Atomowość, niestabilność i niezmienność są różne, część pierwsza] (http://blogs.msdn.com/b/ericlippert/archive/2011/ 05/26/lotność-zmienność-i-niezmienność-są-różne-part-one.aspx) – InBetween

Powiązane problemy