ulotne (programowanie komputerowe) - volatile (computer programming)


W programowaniu komputerowym , szczególnie w językach programowania C , C++ , C# i Java , słowo kluczowe volatile wskazuje, że wartość może się zmieniać między różnymi dostępami, nawet jeśli nie wydaje się, aby została zmodyfikowana. To słowo kluczowe uniemożliwia kompilatorowi optymalizującemu optymalizację kolejnych odczytów lub zapisów, a tym samym niepoprawne ponowne użycie nieaktualnej wartości lub pominięcie zapisów. Wartości ulotne pojawiają się przede wszystkim w dostępie do sprzętu ( wejścia/wyjścia mapowane w pamięci ), gdzie odczyt lub zapis do pamięci jest używany do komunikacji z urządzeniami peryferyjnymi oraz w wątkach , gdzie inny wątek mógł zmodyfikować wartość.

Pomimo tego, że jest powszechnym słowem kluczowym, zachowanie volatileróżni się znacznie w zależności od języka programowania i łatwo jest go źle zrozumieć. W C i C++ jest to kwalifikator typu , jak consti jest właściwością typu . Co więcej, w C i C++ nie działa w większości scenariuszy wątków, a takie użycie jest odradzane. W Javie i C# jest to właściwość zmiennej i wskazuje, że obiekt, z którym powiązana jest zmienna, może ulec mutacji i jest specjalnie przeznaczony do obsługi wątków. W języku programowania D istnieje osobne słowo kluczowe shareddla użycia wątków, ale nie volatileistnieje żadne słowo kluczowe.

W C i C++

W C, a co za tym idzie C++, volatilesłowo kluczowe miało na celu:

  • zezwól na dostęp do mapowanych w pamięci urządzeń we/wy
  • zezwól na użycie zmiennych między setjmpilongjmp
  • pozwalają na użycie sig_atomic_tzmiennych w procedurach obsługi sygnałów.

Chociaż zamierzone zarówno przez C, jak i C++, standardy C nie wyrażają, że volatilesemantyka odnosi się do l-wartości, a nie do obiektu, do którego się odwołuje. Odpowiedni raport o defektach DR 476 (do C11) jest nadal analizowany z C17.

Operacje na volatilezmiennych nie są atomowe ani nie ustanawiają właściwej relacji „zdarzy się przed” dla wątków. Jest to określone w odpowiednich standardach (C, C++, POSIX , WIN32), a zmienne nietrwałe nie są bezpieczne wątkowo w zdecydowanej większości obecnych implementacji. Dlatego też użycie volatilesłowa kluczowego jako przenośnego mechanizmu synchronizacji jest odradzane przez wiele grup C/C++.

Przykład odwzorowania we/wy w pamięci w C

W tym przykładzie, kod ustawia wartość przechowywaną w foocelu 0. Następnie zaczyna wielokrotnie odpytywać tę wartość, aż zmieni się na 255:

static int foo;

void bar(void) {
    foo = 0;

    while (foo != 255)
         ;
}

Optymalizacji kompilator nie zauważy, że żaden inny kod może ewentualnie zmienić wartość przechowywaną w foo, i zakłada, że pozostanie ona równa 0w każdym czasie. Kompilator zastąpi zatem ciało funkcji nieskończoną pętlą podobną do tej:

void bar_optimized(void) {
    foo = 0;

    while (true)
         ;
}

Jednak foomoże reprezentować lokalizację, która może być zmieniana przez innych elementów systemu komputerowego w każdej chwili, takich jak rejestru sprzętowego urządzenia podłączonego do procesora . Powyższy kod nigdy nie wykryłby takiej zmiany; bez volatilesłowa kluczowego kompilator zakłada, że ​​bieżący program jest jedyną częścią systemu, która może zmienić wartość (co jest zdecydowanie najczęstszą sytuacją).

Aby uniemożliwić kompilatorowi optymalizację kodu jak powyżej, volatileużywane jest słowo kluczowe:

static volatile int foo;

void bar (void) {
    foo = 0;

    while (foo != 255)
        ;
}

Dzięki tej modyfikacji stan pętli nie zostanie zoptymalizowany, a system wykryje zmianę, gdy się pojawi.

Ogólnie rzecz biorąc, istnieją operacje bariery pamięci dostępne na platformach (ujawnione w C++11), które powinny być preferowane zamiast nietrwałych, ponieważ pozwalają kompilatorowi na lepszą optymalizację i, co ważniejsze, gwarantują poprawne zachowanie w scenariuszach wielowątkowych; ani specyfikacja C (przed C11), ani specyfikacja C++ (przed C++11) nie określają modelu pamięci wielowątkowej, więc volatile może nie zachowywać się deterministycznie pomiędzy systemami operacyjnymi/kompilatorami/CPU.

Porównanie optymalizacji w C

Poniższe programy w języku C i towarzyszące im zestawy pokazują, jak volatilesłowo kluczowe wpływa na dane wyjściowe kompilatora. Kompilatorem w tym przypadku był GCC .

Obserwując kod asemblera wyraźnie widać, że kod generowany z volatileobiektami jest bardziej rozwlekły, przez co wydłuża się, aby volatilemożna było spełnić charakter obiektów. W volatilezapobiega słów kluczowych kompilator z wykonywania optymalizacja kodu udziałem lotnych obiektów, zapewniając w ten sposób, że każda zmienna i niestabilna przypisanie odczytu posiada odpowiedni dostęp do pamięci. Bez volatilesłowa kluczowego kompilator wie, że zmienna nie musi być ponownie odczytywana z pamięci przy każdym użyciu, ponieważ nie powinno być żadnych zapisów do jego lokalizacji w pamięci z żadnego innego wątku lub procesu.

C++11

Zgodnie ze standardem ISO C++11 , słowo kluczowe volatile jest przeznaczone tylko do dostępu do sprzętu; nie używaj go do komunikacji między wątkami. Do komunikacji między wątkami biblioteka standardowa udostępnia std::atomic<T>szablony.

w języku Java

Język programowania Java również zawiera volatilesłowo kluczowe, ale jest ono używane w nieco innym celu. Po zastosowaniu do pola kwalifikator Java volatilezapewnia następujące gwarancje:

  • We wszystkich wersjach Javy istnieje globalne uporządkowanie odczytów i zapisów wszystkich zmiennych lotnych (to globalne uporządkowanie zmiennych niestabilnych jest porządkiem częściowym względem większego porządku synchronizacji (który jest porządkiem całkowitym dla wszystkich działań synchronizacji )). Oznacza to, że każdy wątek uzyskujący dostęp do pola ulotnego odczyta swoją bieżącą wartość przed kontynuowaniem, zamiast (potencjalnie) używać wartości z pamięci podręcznej. (Nie ma jednak gwarancji co do względnej kolejności niestabilnych odczytów i zapisów przy regularnych odczytach i zapisach, co oznacza, że ​​generalnie nie jest to przydatna konstrukcja wątkowa).
  • W Javie 5 lub nowszych, niestabilne odczyty i zapisy ustanawiają relację „zdarzy się przed” , podobnie jak pozyskiwanie i zwalnianie muteksu.

Używanie volatilemoże być szybsze niż lock , ale nie będzie działać w niektórych sytuacjach przed Javą 5. Zakres sytuacji, w których volatile jest skuteczny, został rozszerzony w Javie 5; w szczególności podwójne sprawdzanie blokowania działa teraz poprawnie.

W C#

W języku C # , volatilezapewnia, że kod dostępu pola nie podlega niektórych optymalizacje nitki niebezpieczne, które mogą być wykonywane przez kompilator, CLR lub sprzętowo. Gdy pole jest oznaczone volatile, kompilator otrzymuje polecenie wygenerowania „bariery pamięci” lub „ogrodzenia” wokół niego, co uniemożliwia zmianę kolejności instrukcji lub buforowanie powiązane z polem. Podczas odczytywania volatilepola kompilator generuje ogrodzenie nabywania , które zapobiega przenoszeniu innych odczytów i zapisów w polu, w tym w innych wątkach, przed ogrodzenie. Podczas zapisywania do volatilepola kompilator generuje ograniczenie zwolnienia ; Zapobiega to ogrodzenie inny odczytuje i zapisuje do pola z przemieszczane po ogrodzeniu.

Tylko następujące typy mogą być oznakowane volatile: wszystkie typy referencyjne, Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Char, i wszystkie typy wyliczeniowe z bazowego typu Byte, SByte, Int16, UInt16, Int32, lub UInt32. (Z wyłączeniem wartość konstrukcjom , jak również pierwotne typu Double, Int64, UInt64i Decimal).

Użycie volatilesłowa kluczowego nie obsługuje pól, które są przekazywane przez referencję lub przechwyconych zmiennych lokalnych ; w takich przypadkach Thread.VolatileReadi Thread.VolatileWritemusi być używany zamiast tego.

W efekcie metody te wyłączają niektóre optymalizacje zwykle wykonywane przez kompilator C#, kompilator JIT lub sam procesor. Gwarancje zapewniane przez Thread.VolatileReadi Thread.VolatileWritesą nadzbiorem gwarancji zapewnianych przez volatilesłowo kluczowe: zamiast generowania „pół ogrodzenia” (tj. granica nabycia uniemożliwia jedynie zmianę kolejności instrukcji i buforowanie, która jest przed nim) VolatileReadi VolatileWritegeneruje „pełne ogrodzenie”, które zapobiec zmianie kolejności instrukcji i buforowaniu tego pola w obu kierunkach. Metody te działają w następujący sposób:

  • Te Thread.VolatileWritesiły metoda wartość w polu, aby zostać zapisane w punkcie połączenia. Ponadto, wszelkie wcześniejsze wczytywanie i zapisywanie kolejności programu musi nastąpić przed wywołaniem, VolatileWritea wszelkie późniejsze wczytywanie i zapisywanie kolejności programu musi nastąpić po wywołaniu.
  • Te Thread.VolatileReadsiły metoda wartość w polu należy czytać od w punkcie połączenia. Ponadto, wszelkie wcześniejsze wczytywanie i zapisywanie kolejności programu musi nastąpić przed wywołaniem, VolatileReada wszelkie późniejsze wczytywanie i zapisywanie kolejności programu musi nastąpić po wywołaniu.

Thread.VolatileReadI Thread.VolatileWritemetody generowania pełny płot wywołując Thread.MemoryBarriermetodę, która konstruuje barierę pamięci, który działa w obu kierunkach. Oprócz motywów stosowania pełnego ogrodzenia podanych powyżej, jeden potencjalny problem ze volatilesłowem kluczowym rozwiązywanym za pomocą pełnego ogrodzenia generowanego przez Thread.MemoryBarrierjest następujący: ze względu na asymetryczny charakter półogrodzenia volatilepole z instrukcją zapisu, po którym następuje instrukcja read może nadal mieć zamienioną kolejność wykonania przez kompilator. Ponieważ pełne ogrodzenia są symetryczne, nie stanowi to problemu przy użyciu Thread.MemoryBarrier.

W Fortranie

VOLATILEjest częścią standardu Fortran 2003 , chociaż wcześniejsza wersja obsługiwała go jako rozszerzenie. Wykonywanie wszystkich zmiennych volatilew funkcji jest również przydatna stwierdzenie aliasing związanych błędów.

integer, volatile :: i ! When not defined volatile the following two lines of code are identical
write(*,*) i**2  ! Loads the variable i once from memory and multiplies that value times itself
write(*,*) i*i   ! Loads the variable i twice from memory and multiplies those values

Zawsze „drążając” do pamięci VOLATILE, kompilator Fortran nie może zmieniać kolejności odczytów lub zapisów na lotne. Dzięki temu akcje wykonywane w tym wątku są widoczne dla innych wątków i na odwrót.

Użycie VOLATILE zmniejsza, a nawet może uniemożliwić optymalizację.

Bibliografia

Zewnętrzne linki