Pergunta

Vamos dizer que uma classe tem um campo public int counter que é acessado por vários segmentos. Este int só é aumentado ou diminuído.

Para incrementar este campo, que abordagem deve ser usada, e por quê?

  • lock(this.locker) this.counter++;,
  • Interlocked.Increment(ref this.counter);,
  • Alterar o modificador de acesso de counter para public volatile.

Agora que eu descobri volatile, eu fui removendo muitas declarações lock eo uso de Interlocked. Mas há uma razão para não fazer isso?

Foi útil?

Solução

Pior (não vai realmente trabalho)

Alterar o modificador de acesso de counter para public volatile

Como outras pessoas mencionados, este por si só não é realmente seguro em tudo. O ponto de volatile é que múltiplos threads em execução em vários CPUs pode e irá armazenar em cache de dados e instruções de re-ordem.

Se é não volatile e CPU A incrementa um valor, então CPU B não pode realmente ver que valor incrementado até algum tempo depois, o que pode causar problemas.

Se for volatile, este apenas garante as duas CPUs ver os mesmos dados ao mesmo tempo. Ele não pára por eles em tudo a partir de intercalação sua lê e operações de escrita que é o problema que você está tentando evitar.

Segundo Melhor:

lock(this.locker) this.counter++;

Esta é seguro fazê-(desde que você lembre-se de lock em qualquer outro lugar que você acessar this.counter). Ela impede quaisquer outros tópicos de executar qualquer outro código que é guardado por locker. Usando fechaduras também, previne a multi-CPU reordenação problemas como acima, que é excelente.

O problema é, o bloqueio é lento, e se você reutilizar o locker em algum outro lugar que não está realmente relacionado, então você pode acabar bloqueando seus outros tópicos sem nenhuma razão.

Melhor

Interlocked.Increment(ref this.counter);

Isto é seguro, uma vez que efetivamente faz a leitura, incremento, e escrever em 'one hit', que não pode ser interrompido. Devido a isso, ele não afetará qualquer outro código, e você não precisa se lembrar de trancar em outros lugares também. É também muito rápido (como diz MSDN, em CPUs modernas, esta é muitas vezes literalmente, uma única instrução CPU).

eu não estou totalmente certo no entanto, se ele fica em torno de outras CPUs reordenação coisas, ou se você também precisa combinar volátil, com o incremento.

InterlockedNotes:

  1. MÉTODOS Encaixado SÃO CONCURRENTLY SAFE em qualquer número de núcleos ou CPUs.
  2. métodos bloqueadas aplicar uma cerca completa em torno instruções que executam, de modo reordenação não acontece.
  3. métodos bloqueadas Não é necessário ou mesmo não suportam acesso a um campo volátil , tão volátil é colocado uma meia cerca em torno de operações em determinado domínio e interligados está usando a vedação completa.

Nota de rodapé: O volátil é realmente bom para

.

Como volatile não impede que esses tipos de questões multithreading, o que é isso? Um bom exemplo está dizendo que você tem duas linhas, uma que sempre escreve a uma variável (queueLength digamos), e que sempre lê a partir dessa mesma variável.

Se queueLength não é volátil, linha A, pode escrever cinco vezes, mas thread B pode ver essas gravações como sendo adiada (ou mesmo potencialmente na ordem errada).

Uma solução seria a fechadura, mas você também pode usar volátil nesta situação. Isto assegura que o thread B sempre verá a coisa mais up-to-date que thread A escreveu. Nota, contudo, que esta lógica única funciona se você tiver escritores que nunca ler, e os leitores que nunca escrever, e , se a coisa que você está escrevendo é um valor atômico. Assim que você faz uma única leitura modificar-escrever, você precisa ir para operações bloqueadas ou usar um Lock.

Outras dicas

EDIT: Como observado nos comentários, esses dias eu estou feliz em usar Interlocked para os casos de um variável única , onde é , obviamente, OK. Quando se fica mais complicado, eu vou ainda revert para bloqueio ...

Usando volatile não vai ajudar quando você precisa de incremento - porque a leitura ea escrita são instruções separadas. Outro segmento pode alterar o valor depois que você ler, mas antes de escrever de volta.

Pessoalmente, eu quase sempre apenas bloquear - é mais fácil para obter direito de uma maneira que é , obviamente, certo que qualquer volatilidade ou Interlocked.Increment. Tanto quanto eu estou em causa, livre de bloqueio multi-threading é para especialistas verdadeira segmentação, do qual eu não sou um deles. Se Joe Duffy e seus bibliotecas equipe de construção agradáveis ??que irá parallelise coisas sem tanta bloqueio como algo que eu construir, isso é fabuloso, e eu vou usá-lo num piscar de olhos - mas quando eu estou fazendo a enfiar-me, eu tento mantê-lo simples.

"volatile" não substitui Interlocked.Increment! Ele só garante que a variável não está em cache, mas usado diretamente.

Incrementar uma variável requer realmente três operações:

  1. ler
  2. incremento
  3. write

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

De qualquer bloqueio ou incremento intertravado é o que você está procurando.

volátil não é definitivamente o que você está depois -. Ele simplesmente diz o compilador para tratar a variável como sempre mudando, mesmo que o caminho de código atual permite que o compilador para otimizar a leitura da memória de outra forma

por exemplo.

while (m_Var)
{ }

Se m_Var é definido como falso em outro segmento, mas não é declarada como volátil, o compilador é livre para fazê-lo um loop infinito (mas não significa que sempre será), tornando-a verificar contra um registo CPU (por exemplo EAX porque era isso que m_Var foi buscada em desde o início) em vez de emitir uma outra leitura para o local de memória de m_Var (pode ser armazenada em cache - não sabemos e não se importam e que é o ponto de coerência de cache de x86 / 64 x). Todas as mensagens anteriores por outros que mencionados instrução reordenação simplesmente mostrar que eles não entendem x86 / arquiteturas x64. Volátil não questão de leitura / gravação barreiras como implicado pelos posts anteriores dizendo 'que impede reordenação'. Na verdade, graças novamente para protocolo MESI, temos a garantia o resultado que lemos é sempre o mesmo em CPUs independentemente dos resultados reais foram retirados para a memória física ou simplesmente residir no cache da CPU local. Eu não vou ir longe demais em detalhes sobre isso, mas a certeza de que, se isso der errado, Intel / AMD provavelmente emitir um recall processador! Isto também significa que não terá de se preocupar fora de execução de ordens, etc. Os resultados são sempre garantida a se aposentar no fim - caso contrário, são recheadas

Com Interlocked Incremento, as necessidades de processadores para sair, buscar o valor a partir do endereço dado, então incremento e escrevê-lo de volta - tudo o que ao ter a propriedade exclusiva de toda a linha de cache (bloqueio xadd), enquanto para garantir que nenhum outro processadores 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, o processador (CPUA) não pede a propriedade exclusiva da linha de cache ao fazer tudo o que fez com a versão intertravado. Como você pode imaginar, isso significa que outros processadores poderia escrever um valor de volta atualizado para m_Var depois de ter sido lido por CPUA. Assim em vez de agora ter incrementado o valor duas vezes, você acaba com apenas uma vez.

Espero que isso esclarece a questão.

Para obter mais informações, consulte 'compreender o impacto de técnicas de baixo-Lock em Multithreaded Apps' - http://msdn.microsoft.com/en-au/magazine/cc163715.aspx

P.S. O que motivou esta resposta muito tarde? Todas as respostas foram tão descaradamente incorretas (especialmente a um marcado como a resposta) em sua explicação eu só tinha para esclarecê-lo para qualquer outra pessoa lendo isso. encolhe os ombros

p.p.s. Estou assumindo que o alvo é x86 / x64 e não IA64 (ele tem um modelo de memória diferente). Note-se que as especificações ECMA da Microsoft é asneira na medida em que especifica o modelo de memória mais fraca em vez do mais forte (que é sempre melhor para especificar contra o modelo de memória mais forte por isso é consistente em todas as plataformas - caso contrário, o código que seria executado 24-7 em x86 / x64 pode não funcionar em todos no IA64 embora a Intel tem implementado modelo de memória igualmente forte para IA64) - Microsoft admitiu-se - http://blogs.msdn.com/b/cbrumme/archive/2003/05/17/51445.aspx .

funções

bloqueadas não bloquear. Eles são atômicas, o que significa que eles podem concluir, sem a possibilidade de uma troca de contexto durante incremento. Portanto, não há chance de impasse ou espera.

Eu diria que você deve sempre preferi-lo a um fechamento e incremento.

Volatile é útil se você precisar escreve em um segmento para ser lido em outro, e se você quiser o otimizador a operações não reordenar em uma variável (porque as coisas estão acontecendo em outro segmento que o otimizador não sabe sobre). É uma escolha ortogonal à forma como você incrementar.

Este é um artigo muito bom se você quiser ler mais sobre o código livre-lock, e a maneira correta de abordar a escrevê-lo

http://www.ddj.com/hpc-high-performance- computação / 210604448

bloqueio (...) obras, mas pode bloquear um fio, e pode causar impasse se outro código é usando os mesmos bloqueios de forma incompatível.

Interlocked. * É a maneira correta de fazê-lo ... muito menos sobrecarga como CPUs modernas apoiar este como um primitivo.

volátil por si só não é correto. Um fio de tentar recuperar e, em seguida, escrever de volta um valor modificado ainda podia conflito com outro segmento fazendo o mesmo.

Eu fiz alguns testes para ver como a teoria realmente funciona: kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html . Meu teste foi mais focada em CompareExchnage mas o resultado de incremento é similar. Interlocked não é necessário mais rápido em ambiente multi-cpu. Aqui está o resultado do teste de incremento em um servidor de 16 CPU de 2 anos de idade. Bare em mente que o teste envolve também 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

Eu gostaria de adicionar à mencionado em outras respostas a diferença entre volatile, Interlocked e lock:

A palavra-chave volátil pode ser aplicado a campos de estes tipos :

  • Tipos de referência.
  • tipos de ponteiro (em um contexto inseguro). Note que, embora o ponteiro em si pode ser volátil, o objeto que ele aponta para não podem. em outra palavras, você não pode declarar um "ponteiro" para ser "volátil".
  • tipos simples, como sbyte, byte, short, ushort, int, uint, char, float e bool.
  • Um tipo de enumeração com um dos seguintes tipos de base:. byte, sbyte, short, ushort, int, ou uint
  • parâmetros de tipo genérico conhecidos por serem tipos de referência.
  • IntPtr e UIntPtr.

Outros tipos , incluindo double e long, não pode ser marcado como "volátil" porque lê e grava em campos desses tipos não podem ser garantidos para ser atômica. Para proteger o acesso multi-thread para esses tipos de campos, use os membros da classe Interlocked ou acesso protect usando o lock comunicado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top