volatile (počítačové programování) - volatile (computer programming)


V počítačovém programování , zejména v programovacích jazycích C , C ++ , C# a Java , volatilní klíčové slovo naznačuje, že se hodnota může mezi různými přístupy měnit, i když se nezdá být upravena. Toto klíčové slovo zabrání optimalizačnímu kompilátoru v optimalizaci vzdálených následných čtení nebo zápisů, a tedy v nesprávném použití zastaralé hodnoty nebo vynechání zápisů. Těkavé hodnoty primárně vznikají v hardwarovém přístupu ( I/O mapované v paměti ), kde se ke komunikaci s periferními zařízeními používá čtení z nebo zápis do paměti , a ve vláknech , kde jiné vlákno mohlo upravit hodnotu.

Přestože jde o běžné klíčové slovo, chování volatilese mezi programovacími jazyky výrazně liší a je snadno nepochopitelné. V jazyce C a C ++ je to kvalifikátor typu consta je vlastností typu . Kromě toho, v C a C ++ to nebude fungovat ve většině závitů scénářů, a že užívání se nedoporučuje. V Javě a C#je to vlastnost proměnné a označuje, že objekt, ke kterému je proměnná vázána, může mutovat, a je konkrétně určen pro navlékání vláken. V programovacím jazyce D existuje samostatné klíčové slovo sharedpro použití vláken, ale žádné volatileklíčové slovo neexistuje.

V C a C ++

V jazyce C a následně v jazyce C ++ bylo volatileklíčové slovo zamýšleno

  • povolit přístup k paměťově mapovaným I/O zařízením
  • umožňují použití proměnných mezi setjmpalongjmp
  • umožňují použití sig_atomic_tproměnných v obsluhách signálů.

I když to zamýšlí C i C ++, standardy C nedokáží vyjádřit, že volatilesémantika odkazuje na hodnotu lvalue, nikoli na odkazovaný objekt. Příslušná zpráva o závadě DR 476 (až C11) je stále předmětem kontroly s C17.

Operace s volatileproměnnými nejsou atomické , ani nezakládají správný vztah před navazováním. To je uvedeno v příslušných normách (C, C ++, POSIX , WIN32) a volatilní proměnné nejsou v drtivé většině současných implementací bezpečné pro vlákna. Proto používání volatileklíčových slov jako přenosného synchronizačního mechanismu odrazuje mnoho skupin C/C ++.

Příklad paměťově mapovaných I/O v C

V tomto příkladu je kód nastaví hodnotu uloženou v fook 0. Poté se začne dotazovat opakovaně tuto hodnotu, dokud se změní na 255:

static int foo;

void bar(void) {
    foo = 0;

    while (foo != 255)
         ;
}

Optimalizace kompilátoru Všimněte si, že žádný jiný kód může případně změnit hodnoty uložené v fooa bude předpokládat, že zůstane rovná 0za všech okolností. Kompilátor proto nahradí tělo funkce nekonečnou smyčkou podobnou této:

void bar_optimized(void) {
    foo = 0;

    while (true)
         ;
}

Nicméně, foomůže představovat umístění, které lze změnit ostatními prvky počítačového systému kdykoliv, jako je hardware registru ze zařízení připojeného k CPU . Výše uvedený kód by nikdy nezaznamenal takovou změnu; bez volatileklíčového slova kompilátor předpokládá, že aktuální program je jedinou částí systému, která může změnit hodnotu (což je zdaleka nejběžnější situace).

Aby se zabránilo kompilátoru v optimalizaci kódu, jak je uvedeno výše, použije se volatileklíčové slovo:

static volatile int foo;

void bar (void) {
    foo = 0;

    while (foo != 255)
        ;
}

Touto úpravou nebude podmínka smyčky optimalizována a systém změnu zjistí, jakmile k ní dojde.

Obecně jsou na platformách (které jsou vystaveny v C ++ 11) k dispozici operace bariéry paměti, které by měly být upřednostňovány před těkavými, protože umožňují kompilátoru provádět lepší optimalizaci a co je důležitější, zaručují správné chování ve vícevláknových scénářích; ani specifikace C (před C11), ani specifikace C ++ (před C ++ 11) nespecifikuje model paměti s více vlákny, takže volatile se nemusí chovat deterministicky napříč operačními systémy/kompilátory/CPU.

Porovnání optimalizace v C.

Následující programy C a doprovodná sestavení ukazují, jak volatileklíčové slovo ovlivňuje výstup kompilátoru. Kompilátorem v tomto případě byl GCC .

Při sledování kódu sestavy je jasně vidět, že kód generovaný volatileobjekty je podrobnější, takže je delší, takže volatilelze splnit povahu objektů. Mezi volatileklíčové slovo zabraňuje překladač od provedení optimalizace na kódu zahrnující těkavé předměty, čímž je zajištěno, že každý úkol volatile proměnnou a čtení má odpovídající přístup k paměti. Bez volatileklíčového slova kompilátor ví, že proměnnou není nutné při každém použití znovu číst z paměti, protože do jejího paměťového umístění by neměly docházet žádné zápisy z žádného jiného vlákna nebo procesu.

C ++ 11

Podle normy C ++ 11 ISO je volatilní klíčové slovo určeno pouze pro použití pro přístup k hardwaru; nepoužívejte jej pro komunikaci mezi vlákny. Pro komunikaci mezi vlákny poskytuje standardní knihovna std::atomic<T>šablony.

V Javě

Programovací jazyk Java má také volatileklíčové slovo, ale to se používá k poněkud jinému účelu. Při použití na pole volatileposkytuje kvalifikátor Java následující záruky:

  • Ve všech verzích Javy existuje globální řazení při čtení a zápisu všech volatilních proměnných (toto globální řazení u volatil je částečné pořadí oproti většímu synchronizačnímu pořadí (což je celkové pořadí všech synchronizačních akcí )). To znamená, že každé vlákno přistupující k volatilnímu poli bude číst svoji aktuální hodnotu, než bude pokračovat, místo (potenciálně) pomocí hodnoty uložené v mezipaměti. (Neexistuje však žádná záruka relativního uspořádání těkavých čtení a zápisů s pravidelnými čteními a zápisy, což znamená, že to obecně není užitečná konstrukce vláken.)
  • V Javě 5 nebo novější, volatile čtení a zápisy navazují vztah typu happening-before , podobně jako získávání a uvolňování mutexu.

Používání volatilemůže být rychlejší než zámek , ale v některých situacích před Javou 5 nebude fungovat. Rozsah situací, ve kterých je volatilní účinný, byl v Javě 5 rozšířen; zejména zamykání s dvojitou kontrolou nyní funguje správně.

V C#

V C # , volatileje zajištěno, že kód přístupu na pole, není předmětem některých vláknitých nebezpečné optimalizace, které mohou být prováděny kompilátor, CLR, nebo hardwarem. Když je pole označeno volatile, kompilátor dostane pokyn, aby kolem něj vygeneroval „paměťovou bariéru“ nebo „plot“, což zabrání změně pořadí instrukcí nebo ukládání do mezipaměti vázané na pole. Při čtení volatilepole kompilátor vygeneruje akviziční plot , který zabrání přesunu dalších čtení a zápisů do pole, včetně těch v jiných vláknech, před plot. Při zápisu do volatilepole kompilátor generuje plot pro uvolnění ; tento plot zabraňuje jiný čte a zapisuje do pole od přesouvány po plotu.

Pouze tyto typy mohou být označeny volatile: všechny typy odkazů, Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Chara všechny vyjmenované druhy s podkladovým typu Byte, SByte, Int16, UInt16, Int32nebo UInt32. (To se netýká cení structs , stejně jako primitivní typy Double, Int64, UInt64a Decimal).

Použití volatileklíčového slova nepodporuje pole, která jsou předávána referencí nebo zachycenými lokálními proměnnými ; v těchto případech Thread.VolatileReada Thread.VolatileWritemusí být místo toho použit.

Ve skutečnosti tyto metody deaktivují některé optimalizace obvykle prováděné kompilátorem C#, kompilátorem JIT nebo samotným CPU. Záruky poskytované Thread.VolatileReada Thread.VolatileWritejsou nadmnožinou záruk poskytovaných volatileklíčovým slovem: namísto generování „polovičního plotu“ (tj. Akviziční plot brání pouze změně pořadí instrukcí a ukládání do mezipaměti, které jsou před ním), VolatileReada VolatileWritegenerují „úplný plot“, který zabránit změně pořadí instrukcí a ukládání do mezipaměti tohoto pole v obou směrech. Tyto metody fungují následovně:

  • Tyto Thread.VolatileWritemetody síly, je hodnota v poli, které mají být zapsány do v okamžiku volání. Kromě toho jakékoli dřívější načtení a uložení programové objednávky musí proběhnout před voláním VolatileWritea jakákoli pozdější načtení a uložení programové objednávky musí nastat po volání.
  • Tyto Thread.VolatileReadmetody síly, je hodnota v poli je třeba číst v okamžiku volání. Kromě toho jakékoli dřívější načtení a uložení programové objednávky musí proběhnout před voláním VolatileReada jakákoli pozdější načtení a uložení programové objednávky musí nastat po volání.

Metody Thread.VolatileReada Thread.VolatileWritegenerují úplný plot voláním Thread.MemoryBarriermetody, která vytváří paměťovou bariéru, která funguje v obou směrech. Kromě výše uvedených motivací pro použití plného plotu je jeden potenciální problém s volatileklíčovým slovem, které je vyřešeno pomocí generovaného úplného plotu Thread.MemoryBarrier, následující: vzhledem k asymetrické povaze polovičních plotů volatilepole s instrukcí zápisu následovanou u instrukce pro čtení může být stále vykonávací příkaz zaměněn kompilátorem. Protože plné ploty jsou symetrické, není při používání problém Thread.MemoryBarrier.

Ve Fortranu

VOLATILEje součástí standardu Fortran 2003 , ačkoli dřívější verze jej podporovala jako rozšíření. Vytváření všech proměnných volatileve funkci je také užitečné při hledání aliasing souvisejících chyb.

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

Tím, že kompilátor Fortran vždy „přejde“ do paměti VOLATILE, je znemožněno přeskupovat čtení nebo zápisy do těkavých látek. Díky tomu jsou viditelné pro ostatní vlákna akce provedené v tomto vlákně a naopak.

Použití VOLATILE snižuje a dokonce může bránit optimalizaci.

Reference

externí odkazy