Zmienna ulotna

Z Wikipedii, wolnej encyklopedii
Przejdź do nawigacji Przejdź do wyszukiwania

Zmienna ulotna – w programowaniu, słowo kluczowe volatile oznacza, że zmienna lub obiekt mogą zmienić się pomiędzy różnymi odczytami, nawet jeśli wydaje się, jakby nie były zmodyfikowane. Zastosowanie go powstrzymuje kompilator optymalizujący w wypadku kolejnych odczytów lub zapisów zmiennej przed zastąpieniem jej tymczasową stałą, lub pomijaniem nadpisań. Zmienne ulotne pojawiają się przede wszystkim w dostępie do sprzętu, gdzie korzystanie z pamięci jest wykorzystywane do komunikacji pomiędzy urządzeniami oraz w środowisku wielowątkowym, w którym różne wątki mogą korzystać z tej samej zmiennej.

Pomimo bycia powszechnym słowem kluczowym dokładne zachowanie volatile różni się pomiędzy językami programowania. W C i C++ jest modyfikatorem do typu podobnie jak słowo kluczowe const i nie sprawdza się w większości szablonów programów wielowątkowych, dlatego jego zastosowanie jest odradzane. W językach C# i Java jest przeznaczone specjalnie do wielowątkowości — charakteryzuje zmienną i oznacza, że obiekt, z którym jest ona powiązana, może się zmienić.

C oraz C++[edytuj | edytuj kod]

W C i następnie w C++ słowo volatile miało spełniać następujące założenia:[1]

  • dawać dostęp do urządzeń MMIO
  • pozwalać na korzystanie ze zmiennych pomiędzy setjmp i longjmp
  • pozwalać na używanie zmiennych sig_atomic_t w uchwytach sygnałowych (ang. signal handlers)

Operacje na zmiennych ulotnych nie są operacjami atomowymi ani też nie ustanawiają prawidłowiej relacji happens-before (określa w jakiej względnej kolejności wykonywane są instrukcje). Jest to określone w odpowiednich standardach (C, C++, POSIX, WIN32)[1]. Zmienne ulotne nie są bezpieczne dla zdecydowanej większości dzisiejszych implementacji programów wielowątkowych, dlatego też użycie volatile jako przenośnego mechanizmu synchronizacji jest odradzane[2][3].

Przykład zastosowania w C[edytuj | edytuj kod]

Poniższy kod inicjuje zmienną foo na 0 i wykonuje pętlę while dopóki foo nie jest równe 255:

static int foo;

void var(void) {
    foo = 0;
    
    while (foo != 255){
        /* ... */
    }
}

Kompilator optymalizujący zauważy, że żaden inny kod nie może zmienić wartości foo, dlatego założy, że pozostanie ona równa 0. Zamieni wtedy warunek wewnątrz pętli na:

static int foo;

void var(void) {
    foo = 0;
    
    while (true){
        /* ... */
    }
}

Problem pojawia się, gdy foo reprezentuje na przykład pewną lokację w pamięci, która może być zmieniona przez inne elementy systemu w dowolnej chwili (np. rejest urządzeń lub CPU). Powyższy kod nigdy nie wykryłby takiej zmiany — bez volatile kompilator zakłada, że tylko bieżący program może zmienić wartość foo. Aby zapobiec intruzyjnej optymalizacji kompilatora należny zastosować volatile w następujący sposób:

static volatile int foo;

void var(void) {
    foo = 0;
    
    while (foo != 255){
        /* ... */
    }
}

Na większości dzisiejszych platform istnieje system bariery pamięci (od C++11), który powinien być wykorzystywany zamiast mechanizmu volatile, ponieważ pozwala kompilatorowi na lepszą optymalizację i zapewnia poprawne zachowanie podczas operacji wielowątkowych; zarówno C (przed C11), jak i C++ (przed C++11) zakładają modelu wielowątkowego dostępu do pamięci, dlatego zachowanie volatile może nie być deterministyczne pomiędzy kompilatorami/procesorami/systemami operacyjnymi[4].

C++11[edytuj | edytuj kod]

Według standardu C++11 ISO słowo kluczowe volatile jest przeznaczone jedynie dla dostępu sprzętowego i nie należy go używać do komunikacji między wątkami — do tego biblioteka STL przeznaczyła szablony std::atomic<T>[3].

Java[edytuj | edytuj kod]

Język Java również posiada słowo kluczowe volatile, lecz ma ona nieco inną specyfikację:

  • We wszystkich wersjach Javy istnieje globalna kolejność odczytów i zapisów do zmiennej z volatile. Dzięki temu każdy wątek mający do niej dostęp odczyta jej obecną wartość przed kontynuacją, zamiast (potencjalnego) wykorzystania zmiennej przechowywanej w pamięci podręcznej.
  • Od Javy 5 zmienne z modyfikatorem volatile zachowują relację happens-before.

Używanie volatile może być szybsze niż blokowanie, ale może też nie działać w niektórych wypadkach[5][6].

Przypisy[edytuj | edytuj kod]

  1. a b Should volatile acquire atomicity and thread visibility semantics?, www.open-std.org [dostęp 2017-09-03].
  2. Why the “volatile” type class should not be used — The Linux Kernel documentation, www.kernel.org [dostęp 2017-09-03] (ang.).
  3. a b volatile (C++), msdn.microsoft.com [dostęp 2017-09-03] (ang.).
  4. http://kerneltrap.org/Linux/Volatile_Superstition.
  5. Fastest Thread-safe Singleton in Java | Literate Java, literatejava.com [dostęp 2017-09-03] (ang.).
  6. Neil Coffey, Double-checked Locking, www.javamex.com [dostęp 2017-09-03].