Volátil vs. bloqueado vs. bloqueado
para aumentar este campo, que abordagem deve ser usada, e porquê?
-
lock(this.locker) this.counter++;
, -
Interlocked.Increment(ref this.counter);
, - muda o modificador de acesso de
counter
parapublic volatile
.
Agora que descobri volatile
, tenho retirado muitas lock
declarações e o uso de Interlocked
. Mas há alguma razão para não fazer isto?
9 answers
Pior (não vai funcionar realmente)
Como outras pessoas mencionaram, isto por si só não é seguro. O ponto deMudar o modificador de acesso de
counter
parapublic volatile
volatile
é que vários threads rodando em vários CPUs podem e irão cache dados e instruções de re-ordem.
Se for Não volatile
, e o CPU A aumenta um valor, então o CPU B pode não ver esse valor aumentado até algum tempo depois, o que pode causar problema.
Se for volatile
, isto apenas garante que as duas CPUs vêem os mesmos dados ao mesmo tempo. Isso não os impede de qualquer forma de interromper suas leituras e escrever operações que é o problema que você está tentando evitar.
Segundo Melhor:
lock(this.locker) this.counter++
;
Isto é seguro de fazer (desde que se lembre de lock
em todo o lado a que tenha acesso this.counter
). Impede qualquer outro tópico de executar qualquer outro código que seja guardado por locker
.
Usar fechaduras além disso, impede que os problemas multi-CPU reordenem como acima, o que é ótimo.
O problema é que o bloqueio é lento, e se você reutilizar o locker
em algum outro lugar que não esteja realmente relacionado, então você pode acabar bloqueando seus outros tópicos por nenhuma razão.
Melhor
Isto é seguro, uma vez que faz a leitura, o incremento e escreve em 'um hit' que não pode ser interrompido. Por causa disto, não afectará nenhum outro código, e tu não ... também preciso de me lembrar de trancar noutro sítio. Também é muito rápido (como o MSDN diz, em CPUs modernos, esta é muitas vezes literalmente uma única instrução de CPU).
Interlocked.Increment(ref this.counter);
Não tenho a certeza, no entanto, se ele fica perto de outros CPUs reordenando coisas, ou se você também precisa combinar volátil com o incremento.
Notas interligadas:
- os métodos interligados são simultaneamente seguros em qualquer número de núcleos ou CPUs. Os métodos interligados aplicam uma vedação completa em redor. instruções que executam, para que a reordenação não aconteça.
- os métodos interligados não necessitam nem sequer suportam o acesso a um campo Volátil, uma vez que a Volátil é colocada uma meia vedação em torno das operações num dado campo e a interlocada está a usar a vedação completa.
volatile
não impede este tipo de questões de multithreading, para que serve? Um bom exemplo é dizer que você tem dois fios, um que sempre escreve para uma variável (digamos queueLength
), e uma que sempre lê a partir dessa mesma variável.
Se queueLength
não é volátil, o fio A pode escrever cinco vezes, mas o fio B pode ver essas escritas como sendo adiadas (ou mesmo potencialmente na ordem errada).
EDIT: como indicado nos comentários, nestes dias estou feliz em usar {[[0]} para os casos de uma variável única onde é obviamente OK. Quando ficar mais complicado, continuarei a bloquear...
Usar volatile
não ajuda quando precisa de aumentar-porque a leitura e a escrita são instruções separadas. Outro tópico pode mudar o valor depois de ter lido, mas antes de escrever de volta.
"volatile
" não substitui Interlocked.Increment
! Ele apenas garante que a variável não é Cache, mas usada diretamente.
O aumento de uma variável requer na verdade três operações:
- Leia
- incremento
- escreva
Interlocked.Increment
executa todas as três partes como uma única operação atômica.
É o incremento Bloqueado ou interligado que procura.
Volátil não é definitivamente o que você está atrás - ele simplesmente diz ao compilador para tratar a variável como sempre mudando, mesmo que o caminho de código atual permite que o compilador para otimizar uma leitura a partir da memória de outra forma.
Por exemplo
while (m_Var)
{ }
Se m_Var é definido para falso em outro tópico, mas não é declarado como volátil, o compilador é livre para torná-lo um loop infinito (mas não significa que ele sempre irá) por tornando-seleção contra um registro de CPU (e.g. EAX porque era o que m_Var foi obtida a partir do início) em vez de emitir outra leitura para a localização de memória de m_Var (isso pode ser armazenado em cache - nós não sabemos e não se importam e que é o ponto de coerência de cache de x86/x64). Todos os posts anteriores de outros que mencionaram a reordenação de instruções simplesmente mostram que eles não entendem arquiteturas x86/x64. A Volátil não emite barreiras de leitura/escrita, como implementa o anterior. posts dizendo 'it prevents reordering'. Na verdade, graças novamente ao protocolo MESI, estamos garantidos que o resultado que lemos é sempre o mesmo em toda CPUs, independentemente de se os resultados reais foram retirados para a memória física ou simplesmente residem no cache da CPU local. Eu não vou muito longe nos detalhes disto, mas tenha certeza de que se isso correr mal, Intel/AMD provavelmente emitirá uma recall processador! Isso também significa que não temos que nos preocupar com a execução de ordens, etc. Os resultados são sempre garantido para se aposentar em ordem-caso contrário estamos cheios!
Com incrementos entrelaçados, o processador precisa sair, obter o valor do endereço dado, em seguida, incrementá-lo e escrevê-lo de volta -- tudo isso, enquanto tem a propriedade exclusiva de toda a linha de cache (bloquear xadd) para se certificar de que nenhum outro processador pode modificar o seu valor.
Com Volátil, você ainda vai acabar com apenas 1 instrução (assumindo que o JIT é eficiente como deveria) - inc dword ptr [m_Var]. No entanto, a o processador (cpuA) não pede a propriedade exclusiva da linha de cache enquanto faz tudo o que fez com a versão interligada. Como você pode imaginar, isso significa que outros processadores poderiam escrever um valor atualizado de volta para m_Var depois que ele foi lido pelo cpuA. Então, em vez de agora ter aumentado o valor duas vezes, você acaba com apenas uma vez.
Espero que isto esclareça a questão. Para mais informações, consulte "entenda o impacto das técnicas de bloqueio baixo em aplicações Multithreaded" - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx O que levou a esta resposta tardia? Todas as respostas foram tão descaradamente incorretas (especialmente a marcada como resposta) em sua explicação Eu só tinha que esclarecer para qualquer outra pessoa lendo isso. ombrosP. P. S. presumo que o alvo seja x86/x64 e não IA64 (tem um modelo de memória diferente). Note-se que as especificações ECMA da Microsoft estão lixadas na medida em que especifica o modelo de memória mais fraco em vez de o mais forte (é sempre melhor para especificar contra a forte memória do modelo é consistente entre as plataformas, caso contrário o código que seria executado 24-7 no x86/x64 pode não funcionar em IA64 embora a Intel tenha implementado da mesma forma forte modelo de memória para IA64) - a Microsoft admitiu esta-se - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx.
As funções interligadas não bloqueiam. Eles são atômicos, o que significa que eles podem completar sem a possibilidade de uma mudança de contexto durante o incremento. Portanto, não há hipótese de um impasse ou de esperar.
Eu diria que deves sempre preferi-lo a uma fechadura e crescimento.O Volátil é útil se precisar que as escritas num tópico sejam lidas noutro, e se quiser que o Optimizador não reordene as operações numa variável (porque as coisas estão a acontecer noutro tópico que o optimizer não sabe sobre). É uma escolha ortogonal de como se incrementam.
Este é um artigo muito bom se você quiser ler mais sobre o código de bloqueio livre, e a maneira certa de abordar escrevendo-o
Bloqueio(...) funciona, mas pode bloquear um thread, e pode causar deadlock se outro código está usando as mesmas Fechaduras de uma forma incompatível.
Encravado.* é a maneira correta de fazê-lo ... muito menos por cima, pois os CPUs modernos suportam isso como um primitivo.
Volátil Por si só não está correcto. Um tópico tentando recuperar e então escrever de volta um valor modificado ainda poderia entrar em conflito com outro tópico fazendo o mesmo.
Apoio a resposta do Jon Skeet e quero adicionar os seguintes links para todos os que querem saber mais sobre "volátil" e interligados:
Atomicidade, volatilidade e imutabilidade são diferentes, Parte Dois
Atomicidade, volatilidade e imutabilidade são diferentes, Parte Três
Sayonara Volátil. Imagem instantânea de máquina do Weblog de Joe Duffy como ele apareceu em 2012)
D:\>InterlockVsMonitor.exe 16
Using 16 threads:
InterlockAtomic.RunIncrement (ns): 8355 Average, 8302 Minimal, 8409 Maxmial
MonitorVolatileAtomic.RunIncrement (ns): 7077 Average, 6843 Minimal, 7243 Maxmial
D:\>InterlockVsMonitor.exe 4
Using 4 threads:
InterlockAtomic.RunIncrement (ns): 4319 Average, 4319 Minimal, 4321 Maxmial
MonitorVolatileAtomic.RunIncrement (ns): 933 Average, 802 Minimal, 1018 Maxmial