Alguém pode fornecer uma boa explicação para a palavra-chave volátil em c #? Quais problemas ele resolve e quais não? Em quais casos me poupará o uso do bloqueio?
c#
multithreading
Doron Yaacoby
fonte
fonte
Respostas:
Acho que não há uma pessoa melhor para responder a isso do que Eric Lippert (ênfase no original):
Para uma leitura mais detalhada, consulte:
fonte
volatile
estarão lá em virtude do bloqueioSe você quiser ser um pouco mais técnico sobre o que a palavra-chave volátil faz, considere o seguinte programa (estou usando o DevStudio 2005):
Usando as configurações padrão do compilador otimizado (versão), o compilador cria o seguinte assembler (IA32):
Observando a saída, o compilador decidiu usar o registro ecx para armazenar o valor da variável j. Para o loop não volátil (o primeiro), o compilador atribuiu i ao registro eax. Bastante direto. No entanto, existem alguns bits interessantes - a instrução lea ebx, [ebx] é efetivamente uma instrução nib multibyte, de modo que o loop salta para um endereço de memória alinhado de 16 bytes. O outro é o uso de edx para incrementar o contador de loop em vez de usar uma instrução inc eax. A instrução add reg, reg possui menor latência em alguns núcleos IA32 em comparação com a instrução inc reg, mas nunca possui latência mais alta.
Agora, para o loop com o contador de loop volátil. O contador é armazenado em [esp] e a palavra-chave volátil informa ao compilador que o valor deve sempre ser lido / gravado na memória e nunca atribuído a um registro. O compilador chega ao ponto de não fazer um carregamento / incremento / armazenamento em três etapas distintas (carregar eax, inc eax, salvar eax) ao atualizar o valor do contador, em vez disso, a memória é modificada diretamente em uma única instrução (um add mem , reg). A maneira como o código foi criado garante que o valor do contador de loop esteja sempre atualizado no contexto de um único núcleo da CPU. Nenhuma operação nos dados pode resultar em corrupção ou perda de dados (portanto, não é possível usar a carga / inc / armazenamento, pois o valor pode mudar durante o inc, perdendo assim no armazenamento). Como as interrupções só podem ser reparadas após a conclusão da instrução atual,
Depois de introduzir uma segunda CPU no sistema, a palavra-chave volátil não se protegerá contra a atualização dos dados por outra CPU ao mesmo tempo. No exemplo acima, você precisaria que os dados estivessem desalinhados para obter uma possível corrupção. A palavra-chave volátil não impedirá a corrupção em potencial se os dados não puderem ser manipulados atomicamente, por exemplo, se o contador de loop for do tipo longo (64 bits), serão necessárias duas operações de 32 bits para atualizar o valor, no meio de qual uma interrupção pode ocorrer e alterar os dados.
Portanto, a palavra-chave volátil é boa apenas para dados alinhados que são menores ou iguais ao tamanho dos registros nativos, de modo que as operações sejam sempre atômicas.
A palavra-chave volátil foi concebida para ser usada com operações de E / S nas quais o IO mudava constantemente, mas tinha um endereço constante, como um dispositivo UART mapeado na memória, e o compilador não deve continuar reutilizando o primeiro valor lido do endereço.
Se você estiver lidando com dados grandes ou tiver várias CPUs, precisará de um sistema de bloqueio de nível superior (SO) para lidar com o acesso a dados corretamente.
fonte
Se você estiver usando o .NET 1.1, a palavra-chave volátil será necessária ao fazer o bloqueio com verificação dupla. Por quê? Como antes do .NET 2.0, o cenário a seguir pode fazer com que um segundo thread acesse um objeto não nulo, mas não totalmente construído:
Antes do .NET 2.0, o this.foo podia ser atribuído à nova instância do Foo, antes da execução da execução do construtor. Nesse caso, um segundo thread pode entrar (durante a chamada do thread 1 para o construtor do Foo) e experimentar o seguinte:
Antes do .NET 2.0, você poderia declarar this.foo como volátil para solucionar esse problema. Desde o .NET 2.0, você não precisa mais usar a palavra-chave volátil para realizar o bloqueio com verificação dupla.
A Wikipedia, na verdade, tem um bom artigo sobre o bloqueio verificado duplo e aborda brevemente este tópico: http://en.wikipedia.org/wiki/Double-checked_locking
fonte
foo
? O encadeamento 1 não está bloqueadothis.bar
e, portanto, apenas o encadeamento 1 poderá inicializar o foo em um determinado momento? Quero dizer, você verificar o valor após o bloqueio será liberado novamente, quando de qualquer maneira ele deve ter o novo valor a partir de fios 1.Às vezes, o compilador otimiza um campo e usa um registro para armazená-lo. Se o thread 1 gravar no campo e outro acessar, uma vez que a atualização foi armazenada em um registro (e não na memória), o segundo thread obterá dados obsoletos.
Você pode pensar na palavra-chave volátil dizendo ao compilador "Quero que você armazene esse valor na memória". Isso garante que o segundo thread recupere o valor mais recente.
fonte
Do MSDN : o modificador volátil geralmente é usado para um campo que é acessado por vários threads sem usar a instrução lock para serializar o acesso. O uso do modificador volátil garante que um thread recupere o valor mais atualizado gravado por outro thread.
fonte
O CLR gosta de otimizar as instruções; portanto, quando você acessa um campo no código, ele nem sempre pode acessar o valor atual do campo (pode ser da pilha, etc.). Marcar um campo como
volatile
garante que o valor atual do campo seja acessado pela instrução. Isso é útil quando o valor pode ser modificado (em um cenário sem bloqueio) por um encadeamento simultâneo no seu programa ou algum outro código em execução no sistema operacional.Você obviamente perde alguma otimização, mas mantém o código mais simples.
fonte
Eu encontrei este artigo por Joydip Kanjilal muito útil!
When you mark an object or a variable as volatile, it becomes a candidate for volatile reads and writes. It should be noted that in C# all memory writes are volatile irrespective of whether you are writing data to a volatile or a non-volatile object. However, the ambiguity happens when you are reading data. When you are reading data that is non-volatile, the executing thread may or may not always get the latest value. If the object is volatile, the thread always gets the most up-to-date value
Vou deixar aqui para referência
fonte
Às vezes, o compilador altera a ordem das instruções no código para otimizá-lo. Normalmente, isso não é um problema no ambiente de thread único, mas pode ser um problema no ambiente de vários threads. Veja o seguinte exemplo:
Se você executar t1 e t2, não esperaria saída ou "Valor: 10" como resultado. Pode ser que o compilador alterne a linha dentro da função t1. Se t2 for executado, pode ser que _flag tenha valor 5, mas _value tenha 0. Portanto, a lógica esperada pode ser quebrada.
Para corrigir isso, você pode usar palavras-chave voláteis que podem ser aplicadas ao campo. Esta declaração desativa as otimizações do compilador para que você possa forçar a ordem correta no seu código.
Você deve usar o volátil apenas se realmente precisar, pois, se desabilitar certas otimizações do compilador, isso prejudicará o desempenho. Também não é suportado por todas as linguagens .NET (o Visual Basic não é compatível), portanto dificulta a interoperabilidade da linguagem.
fonte
Portanto, para resumir tudo isso, a resposta correta para a pergunta é: se o seu código estiver sendo executado no tempo de execução 2.0 ou posterior, a palavra-chave volátil quase nunca será necessária e causará mais danos do que benefícios se usada desnecessariamente. IE Nunca o use. MAS, nas versões anteriores do tempo de execução, é necessário o bloqueio adequado de verificação dupla nos campos estáticos. Campos especificamente estáticos cuja classe possui código de inicialização de classe estática.
fonte
vários threads podem acessar uma variável. A atualização mais recente estará na variável
fonte