Przeczytałem kilka artykułów na temat tego volatile
słowa kluczowego, ale nie mogłem znaleźć jego prawidłowego użycia. Czy mógłbyś mi powiedzieć, do czego ma służyć w C # i Javie?
Przeczytałem kilka artykułów na temat tego volatile
słowa kluczowego, ale nie mogłem znaleźć jego prawidłowego użycia. Czy mógłbyś mi powiedzieć, do czego ma służyć w C # i Javie?
Odpowiedzi:
Zarówno dla języka C #, jak i Java, „volatile” mówi kompilatorowi, że wartość zmiennej nigdy nie może być buforowana, ponieważ jej wartość może się zmienić poza zakresem samego programu. Kompilator uniknie wtedy wszelkich optymalizacji, które mogą spowodować problemy, jeśli zmienna zmieni się „poza jego kontrolą”.
Rozważmy ten przykład:
int i = 5;
System.out.println(i);
Kompilator może to zoptymalizować, aby po prostu wydrukować 5, na przykład:
System.out.println(5);
Jeśli jednak istnieje inny wątek, który może się zmienić i
, jest to niewłaściwe zachowanie. Jeśli inny wątek zmieni się i
na 6, zoptymalizowana wersja nadal będzie drukować 5.
volatile
Kluczowe zapobiega takiej optymalizacji i buforowania, jest zatem korzystne, gdy zmienna może być zmienione przez inny gwint.
i
oznaczeniem jako volatile
. W Javie chodzi o relacje zdarzają się przed .
i
jest zmienną lokalną, żaden inny wątek i tak nie może jej zmienić. Jeśli jest to pole, kompilator nie może zoptymalizować wywołania, chyba że tak jest final
. Nie sądzę, aby kompilator mógł dokonywać optymalizacji w oparciu o założenie, że pole „wygląda”, final
gdy nie jest wyraźnie zadeklarowane jako takie.
Aby zrozumieć, co zmienna robi ze zmienną, ważne jest, aby zrozumieć, co się dzieje, gdy zmienna nie jest zmienna.
Gdy dwa wątki A i B uzyskują dostęp do zmiennej nieulotnej, każdy wątek zachowa lokalną kopię zmiennej w swojej lokalnej pamięci podręcznej. Wszelkie zmiany dokonane przez wątek A w jego lokalnej pamięci podręcznej nie będą widoczne dla wątku B.
Gdy zmienne są deklarowane jako zmienne, zasadniczo oznacza to, że wątki nie powinny buforować takiej zmiennej lub innymi słowy, wątki nie powinny ufać wartościom tych zmiennych, chyba że są one odczytywane bezpośrednio z pamięci głównej.
Kiedy więc uczynić zmienną zmienną?
Gdy masz zmienną, do której ma dostęp wiele wątków i chcesz, aby każdy wątek otrzymał najnowszą zaktualizowaną wartość tej zmiennej, nawet jeśli wartość jest aktualizowana przez jakikolwiek inny wątek / proces / poza programem.
Odczyty zmiennych zmiennych nabrały semantyki . Oznacza to, że jest zagwarantowane, że pamięć odczytana ze zmiennej ulotnej wystąpi przed jakimkolwiek następnym odczytem pamięci. Blokuje kompilator przed zmianą kolejności, a jeśli sprzęt tego wymaga (słabo uporządkowany procesor), użyje specjalnej instrukcji, aby sprzęt opróżnił wszystkie odczyty, które wystąpią po odczycie ulotnym, ale zostały spekulacyjnie uruchomione wcześnie, lub procesor może przede wszystkim zapobiegać ich wczesnemu wystawianiu, zapobiegając pojawianiu się jakiegokolwiek spekulacyjnego obciążenia między wydaniem ładunku a jego wycofaniem.
Zapisy zmiennych zmiennych mają semantykę wydania . Oznacza to, że gwarantuje się, że wszelkie zapisy w pamięci do zmiennej nietrwałej będą opóźnione, dopóki wszystkie poprzednie zapisy w pamięci nie będą widoczne dla innych procesorów.
Rozważmy następujący przykład:
something.foo = new Thing();
Jeśli foo
jest zmienną składową w klasie, a inne procesory mają dostęp do instancji obiektu, do której odwołuje się something
, mogą zobaczyć foo
zmianę wartości, zanim zapisy do pamięci w Thing
konstruktorze będą widoczne globalnie! To właśnie oznacza „słabo uporządkowana pamięć”. Może to nastąpić nawet wtedy, gdy kompilator ma wszystkie sklepy w konstruktorze przed sklepem foo
. Jeśli foo
tak, volatile
magazyn foo
będzie miał semantykę wydania, a sprzęt gwarantuje, że wszystkie zapisy przed zapisem foo
są widoczne dla innych procesorów przed zezwoleniem na foo
wystąpienie zapisu .
Jak to możliwe, foo
że pisma są tak źle uporządkowane? Jeśli wstrzymanie linii pamięci podręcznej foo
znajduje się w pamięci podręcznej, a magazyny w konstruktorze przegapiły pamięć podręczną, wówczas sklep może zakończyć się znacznie wcześniej niż zapisy do pamięci podręcznej chybiają.
(Okropna) architektura Itanium firmy Intel miała słabo uporządkowaną pamięć. Procesor użyty w oryginalnym XBox 360 miał słabo uporządkowaną pamięć. Wiele procesorów ARM, w tym bardzo popularny ARMv7-A, ma słabo uporządkowaną pamięć.
Programiści często nie widzą tych wyścigów danych, ponieważ takie rzeczy jak blokady będą tworzyć pełną barierę pamięci, zasadniczo to samo, co pozyskiwanie i zwalnianie semantyki w tym samym czasie. Żadne obciążenia wewnątrz śluzy nie mogą być spekulatywnie wykonywane przed pozyskaniem zamka, są one opóźniane do momentu nabycia zamka. Żadne zapasy nie mogą być opóźnione przez zwolnienie blokady, instrukcja zwalniająca blokadę jest opóźniona do momentu, gdy wszystkie zapisy wykonane wewnątrz zamka będą widoczne globalnie.
Bardziej kompletnym przykładem jest wzorzec „Podwójnie sprawdzone blokowanie”. Celem tego wzorca jest uniknięcie konieczności uzyskiwania blokady w celu leniwego zainicjowania obiektu.
Zaczepiony z Wikipedii:
public class MySingleton {
private static object myLock = new object();
private static volatile MySingleton mySingleton = null;
private MySingleton() {
}
public static MySingleton GetInstance() {
if (mySingleton == null) { // 1st check
lock (myLock) {
if (mySingleton == null) { // 2nd (double) check
mySingleton = new MySingleton();
// Write-release semantics are implicitly handled by marking
// mySingleton with 'volatile', which inserts the necessary memory
// barriers between the constructor call and the write to mySingleton.
// The barriers created by the lock are not sufficient because
// the object is made visible before the lock is released.
}
}
}
// The barriers created by the lock are not sufficient because not all threads
// will acquire the lock. A fence for read-acquire semantics is needed between
// the test of mySingleton (above) and the use of its contents. This fence
// is automatically inserted because mySingleton is marked as 'volatile'.
return mySingleton;
}
}
W tym przykładzie sklepy w MySingleton
konstruktorze mogą nie być widoczne dla innych procesorów przed sklepem mySingleton
. Jeśli tak się stanie, inne wątki, które zaglądają do mySingleton, nie otrzymają blokady i niekoniecznie przejmą zapisy do konstruktora.
volatile
nigdy nie zapobiega buforowaniu. Gwarantuje porządek, w jakim „widzą” inne procesory. Zwolnienie magazynu opóźni magazyn, dopóki wszystkie oczekujące zapisy nie zostaną zakończone i zostanie wydany cykl magistrali, informujący inne procesory o odrzuceniu / zapisaniu zwrotnym linii pamięci podręcznej, jeśli zdarzy się, że mają buforowane odpowiednie linie. Pozyskiwanie obciążenia usunie spekulowane odczyty, zapewniając, że nie będą one przestarzałymi wartościami z przeszłości.
head
i tail
trzeba być lotny, aby zapobiec producent oparł tail
się nie zmieni, i aby zapobiec konsumenta od zakładając, head
nie ulegnie zmianie. Ponadto head
musi być nietrwały, aby zapewnić, że zapisy danych w kolejce są globalnie widoczne, zanim sklep head
będzie widoczny globalnie.
Lotny kluczowe ma różne znaczenia zarówno Java i C #.
Pole może zostać zadeklarowane jako ulotne, w którym to przypadku model pamięci Java zapewnia, że wszystkie wątki widzą spójną wartość zmiennej.
Z odwołania C # do volatile słowa kluczowego :
Słowo kluczowe volatile wskazuje, że pole może zostać zmodyfikowane w programie przez coś takiego, jak system operacyjny, sprzęt lub współbieżnie wykonywany wątek.
W Javie „volatile” jest używane do informowania maszyny JVM, że zmienna może być używana przez wiele wątków w tym samym czasie, więc nie można zastosować pewnych typowych optymalizacji.
W szczególności sytuacja, w której dwa wątki uzyskujące dostęp do tej samej zmiennej działają na oddzielnych procesorach w tej samej maszynie. Procesory bardzo często zapisują w pamięci podręcznej dane, które przechowują, ponieważ dostęp do pamięci jest znacznie wolniejszy niż dostęp do pamięci podręcznej. Oznacza to, że jeśli dane są aktualizowane w CPU1, muszą natychmiast przejść przez wszystkie pamięci podręczne i do pamięci głównej, a nie wtedy, gdy pamięć podręczna zdecyduje się wyczyścić, aby CPU2 mógł zobaczyć zaktualizowaną wartość (ponownie, pomijając wszystkie pamięci podręczne po drodze).
Podczas odczytywania danych, które są nieulotne, wątek wykonawczy może, ale nie musi, zawsze uzyskać zaktualizowaną wartość. Ale jeśli obiekt jest niestabilny, wątek zawsze otrzymuje najbardziej aktualną wartość.
Zmienna to rozwiązanie problemu współbieżności. Aby zsynchronizować tę wartość. To słowo kluczowe jest najczęściej używane w wątkach. Gdy wiele wątków aktualizuje tę samą zmienną.