volátil (programação de computador) - volatile (computer programming)


Na programação de computadores , principalmente no C , C ++ , C # e Java linguagens de programação , o volátil palavra-chave indica que um valor podem variar entre diferentes acessos, mesmo que não parece ser modificado. Esta palavra-chave evita que um compilador de otimização otimize leituras ou gravações subsequentes e, portanto, reutilize incorretamente um valor obsoleto ou omita gravações. Valores voláteis surgem principalmente no acesso hardware ( memória mapeada I / O ), onde ler ou escrever para a memória é usado para se comunicar com dispositivos periféricos , e em rosca , onde um segmento diferente pode ter modificado um valor.

Apesar de ser uma palavra-chave comum, o comportamento de volatiledifere significativamente entre as linguagens de programação e é facilmente mal interpretado. Em C e C ++, é um qualificador de tipo , como const, e é uma propriedade do tipo . Além disso, em C e C ++ não funciona na maioria dos cenários de threading e esse uso é desencorajado. Em Java e C #, é uma propriedade de uma variável e indica que o objeto ao qual a variável está ligada pode sofrer mutação e se destina especificamente a threading. Na linguagem de programação D , há uma palavra-chave separada sharedpara o uso do encadeamento, mas não volatileexiste nenhuma palavra-chave.

Em C e C ++

Em C e, conseqüentemente, C ++, a volatilepalavra - chave destinava-se a

  • permitir acesso a dispositivos de E / S mapeados em memória
  • permitem o uso de variáveis ​​entre setjmpelongjmp
  • permitir o uso de sig_atomic_tvariáveis ​​em manipuladores de sinal.

Embora pretendido por C e C ++, os padrões C falham em expressar que a volatilesemântica se refere ao lvalue, não ao objeto referenciado. O respectivo relatório de defeito DR 476 (a C11) ainda está em revisão com C17.

As operações em volatilevariáveis ​​não são atômicas , nem estabelecem um relacionamento acontece antes adequado para encadeamento. Isso é especificado nos padrões relevantes (C, C ++, POSIX , WIN32) e variáveis voláteis não são threadsafe na grande maioria das implementações atuais. Portanto, o uso de volatilepalavras-chave como um mecanismo de sincronização portátil é desencorajado por muitos grupos C / C ++.

Exemplo de E / S mapeada por memória em C

Neste exemplo, o código define o valor armazenado em foocomo 0. Em seguida, ele começa a pesquisar esse valor repetidamente até que ele mude para 255:

static int foo;

void bar(void) {
    foo = 0;

    while (foo != 255)
         ;
}

Um compilador otimizado notará que nenhum outro código pode alterar o valor armazenado em fooe presumirá que ele permanecerá igual a 0todo o tempo. O compilador, portanto, substituirá o corpo da função por um loop infinito semelhante a este:

void bar_optimized(void) {
    foo = 0;

    while (true)
         ;
}

No entanto, foopode representar um local que pode ser alterado por outros elementos do sistema de computador a qualquer momento, como um registro de hardware de um dispositivo conectado à CPU . O código acima nunca detectaria tal mudança; sem a volatilepalavra - chave, o compilador assume que o programa atual é a única parte do sistema que pode alterar o valor (que é de longe a situação mais comum).

Para evitar que o compilador otimize o código como acima, a volatilepalavra-chave é usada:

static volatile int foo;

void bar (void) {
    foo = 0;

    while (foo != 255)
        ;
}

Com esta modificação, a condição do loop não será otimizada e o sistema detectará a mudança quando ela ocorrer.

Geralmente, existem operações de barreira de memória disponíveis em plataformas (que são expostas em C ++ 11) que devem ser preferidas em vez de voláteis, pois permitem que o compilador execute uma melhor otimização e, mais importante, garantem o comportamento correto em cenários multi-threaded; nem a especificação C (antes de C11) nem a especificação C ++ (antes de C ++ 11) especifica um modelo de memória multi-threaded, portanto, volátil pode não se comportar deterministicamente em sistemas operacionais / compiladores / CPUs.

Comparação de otimização em C

Os programas C a seguir e os assemblies que os acompanham demonstram como a volatilepalavra - chave afeta a saída do compilador. O compilador neste caso foi o GCC .

Ao observar o código do assembly, é claramente visível que o código gerado com os volatileobjetos é mais detalhado, tornando-o mais longo para que a natureza dos volatileobjetos possa ser cumprida. A volatilepalavra-chave evita que o compilador execute a otimização do código que envolve objetos voláteis, garantindo assim que cada atribuição e leitura de variável volátil tenha um acesso de memória correspondente. Sem a volatilepalavra-chave, o compilador sabe uma variável não precisa ser reler a partir da memória em cada uso, pois não deve haver quaisquer gravações para o seu local de memória de qualquer outro processo ou segmento.

C ++ 11

De acordo com o padrão ISO C ++ 11 , a palavra-chave volatile destina-se apenas ao uso para acesso ao hardware; não o use para comunicação entre threads. Para comunicação entre threads, a biblioteca padrão fornece std::atomic<T>modelos.

Em Java

A linguagem de programação Java também tem a volatilepalavra - chave, mas é usada para um propósito um pouco diferente. Quando aplicado a um campo, o qualificador Java volatileoferece as seguintes garantias:

  • Em todas as versões do Java, há uma ordenação global em leituras e gravações de todas as variáveis voláteis (esta ordenação global no voláteis é uma ordem parcial sobre a maior ordem de sincronização (que é uma ordem total sobre todas as acções de sincronização )). Isto implica que cada fio de acesso a um campo volátil irá ler seu valor atual antes de continuar, em vez de (potencialmente) usando um valor em cache. (No entanto, não há garantia sobre a ordem relativa de leituras e gravações voláteis com leituras e gravações regulares, o que significa que geralmente não é uma construção de threading útil.)
  • No Java 5 ou posterior, as leituras e gravações voláteis estabelecem um relacionamento acontece antes , muito parecido com a aquisição e liberação de um mutex.

O uso volatilepode ser mais rápido do que um bloqueio , mas não funcionará em algumas situações antes do Java 5. A gama de situações em que o volátil é eficaz foi expandida no Java 5; Em particular, verificou duas vezes trancar agora funciona corretamente.

Em C #

Em C # , volatilegarante que o código que acessa o campo não está sujeito a algumas otimizações inseguras de thread que podem ser executadas pelo compilador, CLR ou pelo hardware. Quando um campo é marcado volatile, o compilador é instruído a gerar uma "barreira de memória" ou "cerca" em torno dele, o que evita o reordenamento de instruções ou o armazenamento em cache vinculado ao campo. Ao ler um volatilecampo, o compilador gera uma cerca de aquisição , que evita que outras leituras e gravações no campo, incluindo aquelas em outras threads, sejam movidas antes da cerca. Ao gravar em um volatilecampo, o compilador gera uma cerca de liberação ; esta cerca evita que outras leituras e gravações no campo sejam movidas após a cerca.

Apenas os seguintes tipos podem ser marcados volatile: todos os tipos de referência, Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Char, e todos os tipos enumerados com um tipo subjacente de Byte, SByte, Int16, UInt16, Int32, ou UInt32. (Isto exclui valorizar estruturas , bem como os tipos de primitivas Double, Int64, UInt64e Decimal.)

O uso da volatilepalavra - chave não suporta campos que são passados ​​por referência ou variáveis ​​locais capturadas ; nesses casos, Thread.VolatileReade Thread.VolatileWritedeve ser usado em seu lugar.

Na verdade, esses métodos desabilitam algumas otimizações geralmente realizadas pelo compilador C #, pelo compilador JIT ou pela própria CPU. As garantias fornecidas por Thread.VolatileReade Thread.VolatileWritesão um superconjunto das garantias fornecidas pela volatilepalavra-chave: em vez de gerar uma "meia cerca" (ou seja, uma cerca de aquisição apenas evita a reordenação de instruções e o armazenamento em cache que vem antes dela), VolatileReade VolatileWritegera uma "cerca cheia" que evitar a reordenação de instruções e o armazenamento em cache desse campo em ambas as direções. Esses métodos funcionam da seguinte maneira:

  • O Thread.VolatileWritemétodo força o valor no campo a ser gravado no ponto da chamada. Além disso, todos os carregamentos e armazenamentos de pedidos de programas anteriores devem ocorrer antes da chamada para VolatileWritee quaisquer carregamentos e armazenamentos de pedidos de programas posteriores devem ocorrer após a chamada.
  • O Thread.VolatileReadmétodo força o valor no campo a ser lido no ponto da chamada. Além disso, quaisquer cargas e lojas anteriores de ordem programa deve ocorrer antes da chamada para VolatileReade quaisquer cargas e lojas posteriores de ordem programa deve ocorrer após a chamada.

O Thread.VolatileReade Thread.VolatileWritemétodos de gerar uma cerca cheia chamando o Thread.MemoryBarriermétodo, que constrói uma barreira de memória que funciona em ambas as direções. Além das motivações para usar uma cerca completa fornecidas acima, um problema potencial com a volatilepalavra - chave que é resolvido usando uma cerca completa gerada por Thread.MemoryBarrieré o seguinte: devido à natureza assimétrica das meias-cercas, um volatilecampo com uma instrução de escrita seguida por uma instrução de leitura ainda pode ter a ordem de execução trocada pelo compilador. Como as cercas completas são simétricas, isso não é um problema durante o uso Thread.MemoryBarrier.

Em Fortran

VOLATILEfaz parte do padrão Fortran 2003 , embora a versão anterior o suportasse como uma extensão. Criar todas as variáveis volatileem uma função também é útil para localizar bugs relacionados a aliasing .

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

Sempre "pesquisando" a memória de um VOLÁTIL, o compilador Fortran é impedido de reordenar leituras ou gravações em voláteis. Isso torna visível para outras ações de encadeamentos feitas neste encadeamento e vice-versa.

O uso de VOLATILE reduz e pode até impedir a otimização.

Referências

links externos