Volátil vs. bloqueado vs. bloqueado

Digamos que uma classe tem um campo {[[0]} que é acedido por vários tópicos. Este {[1] } só é aumentado ou decrementado.

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 para public 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?

Author: Sam, 2008-09-30

9 answers

Pior (não vai funcionar realmente)

Mudar o modificador de acesso de counter para public volatile

Como outras pessoas mencionaram, isto por si só não é seguro. O ponto de 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

Interlocked.Increment(ref this.counter);

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).

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:

  1. os métodos interligados são simultaneamente seguros em qualquer número de núcleos ou CPUs.
  2. Os métodos interligados aplicam uma vedação completa em redor. instruções que executam, para que a reordenação não aconteça.
  3. 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.
Nota de pé-de-página: para o que é volátil realmente bom. Como 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).

Uma solução seria bloquear, mas também podes usar Volátil nesta situação. Isso garantiria que o tópico B sempre verá a coisa mais atualizada que o tópico A escreveu. Note no entanto que esta lógica funciona se você tem escritores que nunca lêem, e leitores que nunca escrevem, e se a coisa que você está escrevendo é um valor atômico. Assim que você fizer uma única leitura-modificar-escrever, você precisa ir para operações interligadas ou usar um bloqueio.
 765
Author: Orion Edwards, 2018-06-22 06:39:39

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.

Pessoalmente, quase sempre tranco. é mais fácil. para acertar de uma forma que é obviamente certa do que volatilidade ou entrelaçada.Incremento. No que me diz respeito, multi-thread livre de fechaduras é para especialistas de roscagem reais, dos quais eu não sou um. Se o Joe Duffy e a sua equipa construírem boas bibliotecas que vão acompanhar as coisas sem travar tanto como algo que eu construiria, isso é fabuloso, e vou usá - lo num piscar de olhos-mas quando estou a fazer o rosca eu mesmo, tento manter as coisas simples.
 127
Author: Jon Skeet, 2014-03-20 07:38:27

"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:

  1. Leia
  2. incremento
  3. escreva

Interlocked.Increment executa todas as três partes como uma única operação atômica.

 41
Author: Michael Damatov, 2009-09-07 16:11:03

É 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. ombros

P. 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.

 41
Author: Zach Saw, 2011-07-07 11:50:40

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

Http://www.ddj.com/hpc-high-performance-computing/210604448

 15
Author: Lou Franco, 2008-09-30 19:33:39

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.

 11
Author: Rob Walker, 2008-09-30 19:32:09
Fiz um teste para ver como a teoria funciona. kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html o meu teste centrou-se mais na comparação, mas o resultado para o incremento é semelhante. O entrelaçamento não é necessário mais rápido no ambiente multi-cpu. Aqui está o resultado do teste para o incremento em um servidor de 16 CPU de 2 anos de idade. Sem esquecer que o teste também envolve a leitura segura após o aumento, o que é típico no mundo real.
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
 7
Author: Kenneth Xu, 2012-08-21 12:11:58

Lê a referência Em C#. Cobre os pormenores da sua pergunta. Cada um dos Três tem diferentes propósitos e efeitos colaterais.

 4
Author: spoulson, 2008-09-30 19:33:32