Pior (na verdade não vai funcionar)
Altere o modificador de acesso de counter
parapublic volatile
Como outras pessoas mencionaram, isso por si só não é realmente seguro. A questão volatile
é que vários threads em execução em várias CPUs podem e armazenarão em cache os dados e reordenarão as instruções.
Caso contrário volatile
, e a CPU A incrementa um valor, a CPU B pode não ver esse valor incrementado até algum tempo depois, o que pode causar problemas.
Se for volatile
, isso apenas garante que as duas CPUs vejam os mesmos dados ao mesmo tempo. Isso não os impede de intercalar suas operações de leitura e gravação, que é o problema que você está tentando evitar.
Segundo melhor:
lock(this.locker) this.counter++
;
É seguro fazer isso (desde que você lembre-se de lock
qualquer outro lugar que acessar this.counter
). Impede que outros threads executem qualquer outro código que é protegido por locker
. O uso de bloqueios também evita os problemas de reordenação de várias CPUs como acima, o que é ótimo.
O problema é que o bloqueio é lento e, se você reutilizar o locker
em outro lugar que não esteja realmente relacionado, poderá acabar bloqueando os outros threads sem motivo.
Melhor
Interlocked.Increment(ref this.counter);
Isso é seguro, pois efetivamente lê, incrementa e grava em 'um hit' que não pode ser interrompido. Por esse motivo, ele não afetará nenhum outro código, e você também não precisa se lembrar de bloquear em outro lugar. Também é muito rápido (como o MSDN diz, em CPUs modernas, isso geralmente é literalmente uma única instrução de CPU).
No entanto, não tenho muita certeza se ele contorna outras CPUs, reordenando as coisas ou se você também precisa combinar volátil com o incremento.
InterlockedNotes:
- MÉTODOS INTERLOCADOS SÃO CONCORRENTEMENTE SEGUROS EM QUALQUER NÚMERO DE CORES OU CPUs.
- Os métodos intertravados aplicam uma barreira completa em torno das instruções que eles executam, para que a reordenação não ocorra.
- Os métodos intertravados não precisam ou nem oferecem suporte ao acesso a um campo volátil , pois o volátil é colocado meia cerca em torno das operações em um determinado campo e o intertravado está usando a cerca completa.
Nota de rodapé: Para que serve realmente o volátil.
Como volatile
não impede esses tipos de problemas de multithreading, para que serve? Um bom exemplo é dizer que você tem dois threads, um que sempre grava em uma variável (por exemplo queueLength
) e outro que sempre lê a mesma variável.
Se queueLength
não for volátil, o segmento A pode gravar cinco vezes, mas o segmento B pode ver essas gravações como atrasadas (ou mesmo potencialmente na ordem errada).
Uma solução seria bloquear, mas você também pode usar volátil nessa situação. Isso garantiria que o segmento B sempre visse a coisa mais atualizada que o segmento A escreveu. Observe, no entanto, que essa lógica só funciona se você tiver escritores que nunca leem e leitores que nunca escrevem, e se o que você está escrevendo é um valor atômico. Assim que você fizer uma única leitura, modificação e gravação, precisará acessar as operações de bloqueio ou usar um bloqueio.