Para que é usada a palavra-chave "volátil"?

130

Li alguns artigos sobre a volatilepalavra - chave, mas não consegui descobrir seu uso correto. Você poderia me dizer o que deve ser usado em C # e em Java?

Mircea
fonte
1
Um dos problemas com o volátil é que isso significa mais de uma coisa. Ser uma informação para o compilador não fazer otimizações descoladas é um legado em C. Isso também significa que barreiras de memória devem ser usadas no acesso. Mas, na maioria dos casos, apenas custa desempenho e / ou confunde as pessoas. : P
AnorZaken 28/02/19

Respostas:

93

Para C # e Java, "volátil" diz ao compilador que o valor de uma variável nunca deve ser armazenado em cache, pois seu valor pode mudar fora do escopo do próprio programa. O compilador evitará otimizações que possam resultar em problemas se a variável for alterada "fora de seu controle".

Will A
fonte
@ Tom - devidamente anotado, senhor - e alterado.
Will A
11
Ainda é muito mais sutil que isso.
Tom Hawtin - tackline
1
Errado. Não impede o armazenamento em cache. Veja minha resposta.
precisa saber é o seguinte
168

Considere este exemplo:

int i = 5;
System.out.println(i);

O compilador pode otimizar isso para imprimir apenas 5, assim:

System.out.println(5);

No entanto, se houver outro segmento que possa ser alterado i, esse é o comportamento errado. Se outro segmento mudar ipara 6, a versão otimizada ainda imprimirá 5.

A volatilepalavra-chave impede essa otimização e armazenamento em cache e, portanto, é útil quando uma variável pode ser alterada por outro encadeamento.

Sjoerd
fonte
3
Eu acredito que a otimização ainda seria válida com imarcado como volatile. Em Java, trata -se de relacionamentos que acontecem antes .
Tom Hawtin - tackline
Obrigado por postar, de alguma forma volátil tem conexões com bloqueio variável?
214 Mircea
@Mircea: Foi o que me disseram que marcar algo como volátil era tudo: marcar um campo como volátil usaria algum mecanismo interno para permitir que os segmentos vissem um valor consistente para a variável especificada, mas isso não é mencionado na resposta acima ... talvez alguém possa confirmar isso ou não? Graças
npinti
5
@ Sjoerd: Não tenho certeza se entendi este exemplo. Se ifor uma variável local, nenhum outro encadeamento poderá alterá-lo de qualquer maneira. Se for um campo, o compilador não pode otimizar a chamada, a menos que seja final. Não acho que o compilador possa fazer otimizações com base no pressuposto de que um campo "parece" finalquando não é declarado explicitamente como tal.
polygenelubricants
1
C # e java não são C ++. Isso não está correto. Ele não impede o armazenamento em cache e não impede a otimização. Trata-se de semântica de leitura-aquisição e liberação de loja, necessárias em arquiteturas de memória com ordem fraca. É sobre execução especulativa.
Doug65536
40

Para entender o que o volátil faz com uma variável, é importante entender o que acontece quando a variável não é volátil.

  • A variável é não volátil

Quando dois threads A e B estão acessando uma variável não volátil, cada thread mantém uma cópia local da variável em seu cache local. Quaisquer alterações feitas pelo encadeamento A em seu cache local não serão visíveis para o encadeamento B.

  • Variável é volátil

Quando variáveis ​​são declaradas voláteis, significa essencialmente que os threads não devem armazenar em cache essa variável ou, em outras palavras, os threads não devem confiar nos valores dessas variáveis, a menos que sejam lidos diretamente da memória principal.

Então, quando tornar uma variável volátil?

Quando você tem uma variável que pode ser acessada por vários threads e deseja que cada thread obtenha o valor atualizado mais recente dessa variável, mesmo que o valor seja atualizado por qualquer outro thread / processo / fora do programa.

Saurabh Patil
fonte
2
Errado. Não tem nada a ver com "impedir o armazenamento em cache". Trata-se de reordenar, pelo compilador, OU o hardware da CPU através de execução especulativa.
precisa saber é o seguinte
37

Leituras de campos voláteis adquirem semântica . Isso significa que é garantido que a memória lida na variável volátil ocorrerá antes que qualquer memória a seguir seja lida. Ele impede o compilador de fazer a reordenação e, se o hardware exigir (CPU com pouca ordem), ele usará uma instrução especial para fazer com que o hardware libere todas as leituras que ocorram após a leitura volátil, mas que foram especulativamente iniciadas mais cedo, ou a CPU pode impedir que eles sejam emitidos no início, impedindo que ocorra carga especulativa entre a emissão da carga adquirida e sua retirada.

Gravações de campos voláteis têm semântica de liberação . Isso significa que é garantido que qualquer gravação de memória na variável volátil seja atrasada até que todas as gravações de memória anteriores sejam visíveis para outros processadores.

Considere o seguinte exemplo:

something.foo = new Thing();

Se foofor uma variável membro de uma classe e outras CPUs tiverem acesso à instância do objeto mencionada por something, elas poderão ver o valor foomudar antes que a memória gravada no Thingconstrutor seja visível globalmente! É isso que significa "memória fracamente ordenada". Isso pode ocorrer mesmo se o compilador tiver todas as lojas no construtor antes da loja foo. Se foofor volatile, o armazenamento footerá semântica de liberação, e o hardware garante que todas as gravações antes da gravação foosejam visíveis para outros processadores antes de permitir que a gravação fooocorra.

Como é possível que as gravações foosejam reordenadas tão mal? Se a retenção da linha de cache fooestiver no cache e as lojas do construtor não tiverem o cache, é possível que o armazenamento seja concluído muito mais cedo do que as gravações no cache.

A (terrível) arquitetura Itanium da Intel tinha pedido pouco de memória. O processador usado no XBox 360 original tinha pouca memória solicitada. Muitos processadores ARM, incluindo o muito popular ARMv7-A, têm pouca memória solicitada.

Os desenvolvedores geralmente não veem essas corridas de dados porque coisas como bloqueios criarão uma barreira total à memória, essencialmente a mesma coisa que adquirir e liberar semântica ao mesmo tempo. Nenhuma carga dentro do bloqueio pode ser executada especulativamente antes que o bloqueio seja adquirido; eles são adiados até que o bloqueio seja adquirido. Nenhum armazenamento pode ser atrasado em uma liberação de bloqueio, a instrução que libera o bloqueio é adiada até que todas as gravações feitas dentro do bloqueio sejam visíveis globalmente.

Um exemplo mais completo é o padrão "Bloqueio verificado duas vezes". O objetivo desse padrão é evitar a necessidade de sempre obter um bloqueio para inicializar com preguiça um objeto.

Snagged da Wikipedia:

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;
    }
}

Neste exemplo, as lojas no MySingletonconstrutor podem não estar visíveis para outros processadores antes da loja mySingleton. Se isso acontecer, os outros threads que espreitam o mySingleton não adquirem um bloqueio e não capturam necessariamente as gravações no construtor.

volatilenunca impede o armazenamento em cache. O que ele faz é garantir a ordem na qual outros processadores "vêem" gravam. Uma liberação de loja atrasará uma loja até que todas as gravações pendentes sejam concluídas e um ciclo de barramento tenha sido emitido, informando outros processadores para descartar / gravar novamente sua linha de cache, se as linhas relevantes estiverem em cache. Uma aquisição de carga liberará todas as leituras especuladas, garantindo que elas não sejam valores obsoletos do passado.

doug65536
fonte
Boa explicação. Também é um bom exemplo de bloqueio de verificação dupla. No entanto, ainda não tenho certeza sobre quando usar, pois estou preocupado com os aspectos do cache. Se eu escrever uma implementação de fila em que apenas 1 thread estará gravando e apenas 1 thread estará lendo, posso passar sem bloqueios e apenas marcar meus "ponteiros" de cabeça e cauda como voláteis? Quero garantir que tanto o leitor quanto o escritor vejam os valores mais atualizados.
nickdu
Ambos heade tailprecisam ser voláteis para impedir que o produtor assuma tailque não mudará e para impedir que o consumidor assuma headque não mudará. Além disso, headdeve ser volátil para garantir que as gravações de dados da fila sejam globalmente visíveis antes que o armazenamento headseja globalmente visível.
doug65536
+1, termos como mais recente / "mais atualizado", infelizmente, implicam um conceito do valor correto singular. Na realidade, dois concorrentes podem cruzar a linha de chegada ao mesmo tempo - em uma CPU, dois núcleos podem solicitar uma gravação ao mesmo tempo . Afinal, os núcleos não se revezam no trabalho - isso tornaria inútil o multicore. O bom design / pensamento multithread não deve se concentrar em tentar forçar a "novidade" de baixo nível - inerentemente falso, já que uma trava apenas força os núcleos a selecionar arbitrariamente um alto-falante de cada vez, sem justiça - mas, em vez disso, tenta projetar necessidade de um conceito não natural.
AnorZaken
34

A palavra - chave volátil tem significados diferentes em Java e C #.

Java

Na especificação da linguagem Java :

Um campo pode ser declarado volátil; nesse caso, o modelo de memória Java garante que todos os encadeamentos vejam um valor consistente para a variável.

C #

Na referência C # da palavra - chave volátil :

A palavra-chave volátil indica que um campo pode ser modificado no programa por algo como o sistema operacional, o hardware ou um encadeamento em execução simultâneo.

Krock
fonte
Muito obrigado por postar, como eu entendi em Java, ele age como bloquear essa variável em um contexto de thread e, em C #, se usado, o valor da variável pode ser alterado não apenas do programa, fatores externos como o SO podem modificar seu valor ( nenhum bloqueio implícito) ... Por favor, deixe-me saber se eu entendi direito essas diferenças ...
Mircea
@Mircea em Java, não há bloqueio envolvido, apenas garante que o valor mais atualizado da variável volátil será usado.
krock
O Java promete algum tipo de barreira à memória ou é como C ++ e C # prometendo não otimizar a referência?
Steven Sudit 7/08
A barreira da memória é um detalhe de implementação. O que o Java realmente promete é que todas as leituras verão o valor gravado pela gravação mais recente.
Stephen C
1
@StevenSudit Sim, se o hardware exigir uma barreira ou carregar / adquirir ou armazenar / liberar, ele usará essas instruções. Veja minha resposta.
precisa saber é o seguinte
9

Em Java, "volátil" é usado para informar à JVM que a variável pode ser usada por vários encadeamentos ao mesmo tempo, portanto, certas otimizações comuns não podem ser aplicadas.

Notavelmente, a situação em que os dois threads que acessam a mesma variável estão sendo executados em CPUs separadas na mesma máquina. É muito comum que as CPUs armazenem em cache de forma agressiva os dados que contêm, pois o acesso à memória é muito mais lento que o acesso ao cache. Isso significa que, se os dados forem atualizados na CPU1, eles deverão passar imediatamente por todos os caches e pela memória principal, e não quando o cache decidir se limpar, para que a CPU2 possa ver o valor atualizado (novamente desconsiderando todos os caches no caminho).

Thorbjørn Ravn Andersen
fonte
1

Quando você está lendo dados não voláteis, o encadeamento em execução pode ou não obter sempre o valor atualizado. Mas se o objeto for volátil, o encadeamento sempre obtém o valor mais atualizado.

Subhash Saini
fonte
1
Você pode reformular sua resposta?
Anirudha Gupta
a palavra-chave volátil fornecerá o valor mais atualizado em vez do valor em cache.
Subhash Saini
0

Volátil está resolvendo o problema de simultaneidade. Para tornar esse valor sincronizado. Essa palavra-chave é usada principalmente em um encadeamento. Quando vários threads atualizam a mesma variável.

Shiv Ratan Kumar
fonte
1
Eu não acho que "resolve" o problema. É uma ferramenta que ajuda em algumas circunstâncias. Não confie no volátil para situações em que um bloqueio é necessário, como em uma condição de corrida.
Scratte 26/04