Li alguns artigos sobre a volatile
palavra - chave, mas não consegui descobrir seu uso correto. Você poderia me dizer o que deve ser usado em C # e em Java?
Li alguns artigos sobre a volatile
palavra - chave, mas não consegui descobrir seu uso correto. Você poderia me dizer o que deve ser usado em C # e em Java?
Respostas:
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".
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 i
para 6, a versão otimizada ainda imprimirá 5.
A volatile
palavra-chave impede essa otimização e armazenamento em cache e, portanto, é útil quando uma variável pode ser alterada por outro encadeamento.
i
marcado como volatile
. Em Java, trata -se de relacionamentos que acontecem antes .
i
for 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" final
quando não é declarado explicitamente como tal.
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.
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.
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.
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 foo
for 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 foo
mudar antes que a memória gravada no Thing
construtor 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 foo
for volatile
, o armazenamento foo
terá semântica de liberação, e o hardware garante que todas as gravações antes da gravação foo
sejam visíveis para outros processadores antes de permitir que a gravação foo
ocorra.
Como é possível que as gravações foo
sejam reordenadas tão mal? Se a retenção da linha de cache foo
estiver 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 MySingleton
construtor 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.
volatile
nunca 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.
head
e tail
precisam ser voláteis para impedir que o produtor assuma tail
que não mudará e para impedir que o consumidor assuma head
que não mudará. Além disso, head
deve ser volátil para garantir que as gravações de dados da fila sejam globalmente visíveis antes que o armazenamento head
seja globalmente visível.
A palavra - chave volátil tem significados diferentes em Java e C #.
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.
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.
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).
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.
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.