a atribuição de referência é atômica, então por que o Interlocked.Exchange (ref Object, Object) é necessário?

No meu serviço da web asmx multithreaded eu tinha um campo de class _allData do meu próprio tipo SystemData que consiste em alguns List e Dictionary marcado como volatile . Os dados do sistema ( _allData ) são atualizados de vez em quando e eu faço isso criando outro object chamado newData e preencha suas estruturas de dados com novos dados. Quando terminar, acabei de atribuir

 private static volatile SystemData _allData public static bool LoadAllSystemData() { SystemData newData = new SystemData(); /* fill newData with up-to-date data*/ ... _allData = newData. } 

Isso deve funcionar, pois a atribuição é atômica e os encadeamentos que têm a referência a dados antigos continuam sendo usados, e o restante tem os novos dados do sistema logo após a atribuição. No entanto, meu colega disse que, em vez de usar palavras-chave volatile e assigment simples, eu deveria usar InterLocked.Exchange porque ele disse que em algumas plataformas não é garantido que a atribuição de referência seja atômica. Além disso: quando eu declaro the _allData campo the _allData como volatile

 Interlocked.Exchange(ref _allData, newData); 

produz aviso “uma referência a um campo volátil não será tratado como volátil” O que devo pensar sobre isso?

Existem inúmeras perguntas aqui. Considerando-os um de cada vez:

a atribuição de referência é atômica, então por que o Interlocked.Exchange (ref Object, Object) é necessário?

A atribuição de referência é atômica. Interlocked.Exchange não faz apenas atribuição de referência. Ele faz uma leitura do valor atual de uma variável, armazena o valor antigo e atribui o novo valor à variável, tudo como uma operação atômica.

meu colega disse que em algumas plataformas não é garantido que a atribuição de referência seja atômica. Meu colega estava correto?

Não. A atribuição de referência é garantida como atômica em todas as plataformas .NET.

Meu colega está raciocinando de falsas premissas. Isso significa que suas conclusões estão incorretas?

Não necessariamente. Seu colega pode estar lhe dando bons conselhos por motivos ruins. Talvez haja algum outro motivo pelo qual você deveria estar usando o Interlocked.Exchange. A programação livre de bloqueios é insanamente difícil e, no momento em que você se distancia de práticas bem estabelecidas defendidas por especialistas na área, você está perdido nas ervas daninhas e arriscando o pior tipo de condições de corrida. Eu não sou especialista nesse campo nem especialista em seu código, então não posso fazer um julgamento de uma forma ou de outra.

produz aviso “uma referência a um campo volátil não será tratado como volátil” O que devo pensar sobre isso?

Você deve entender por que isso é um problema em geral. Isso levará a uma compreensão de por que o aviso não é importante nesse caso específico.

A razão pela qual o compilador fornece esse aviso é porque marcar um campo como volátil significa “esse campo será atualizado em vários encadeamentos – não gere nenhum código que armazene em cache valores desse campo e garanta que quaisquer leituras ou este campo não é “movido para frente e para trás no tempo” através de inconsistências do cache do processador. ”

(Eu suponho que você já entendeu tudo isso. Se você não tem uma compreensão detalhada do significado de volatile e como isso impacta a semântica do cache de processador, então você não entende como funciona e não deveria estar usando volatile. são muito difíceis de acertar, certifique-se de que seu programa está certo porque você entende como funciona, e não por acaso.)

Agora, suponha que você crie uma variável que seja um alias de um campo volátil passando uma referência a esse campo. Dentro do método chamado, o compilador não tem nenhum motivo para saber que a referência precisa ter uma semântica volátil! O compilador gerará alegremente código para o método que falha em implementar as regras para campos voláteis, mas a variável é um campo volátil. Isso pode destruir completamente sua lógica livre de bloqueio; a suposição é sempre que um campo volátil é sempre acessado com semântica volátil. Não faz sentido tratá-lo como volátil às vezes e não em outras ocasiões; você tem que ser sempre consistente, caso contrário você não pode garantir consistência em outros accesss.

Portanto, o compilador avisa quando você faz isso, porque ele provavelmente vai bagunçar completamente sua lógica livre de bloqueio cuidadosamente desenvolvida.

Claro, Interlocked.Exchange é escrito para esperar um campo volátil e fazer a coisa certa. O aviso é, portanto, enganoso. Eu me arrependo muito disso; o que deveríamos ter feito é implementar algum mecanismo pelo qual um autor de um método como o Interlocked.Exchange poderia colocar um atributo no método dizendo “este método, que leva um ref, impõe a semântica volátil na variável, portanto, suprima o aviso”. Talvez em uma versão futura do compilador façamos isso.

Ou o seu colega está enganado, ou ele sabe algo que a especificação da linguagem C # não faz.

5.5 Atomicidade de referências variables :

“As leituras e gravações dos seguintes tipos de dados são atômicas: bool, char, byte, sbyte, short, ushort, uint, int, float e tipos de referência.”

Então, você pode escrever para a referência volátil sem o risco de obter um valor corrompido.

Você deve ter cuidado com a maneira como decide qual encadeamento deve buscar os novos dados, para minimizar o risco de que mais de um encadeamento por vez faça isso.

Interlocked.Exchange

Define uma variável do tipo especificado T para um valor especificado e retorna o valor original, como uma operação atômica.

Ele muda e retorna o valor original, é inútil porque você só quer mudá-lo e, como Guffa disse, já é atômico.

A menos que um profiler tenha provado que é um gargalo no seu aplicativo, você deve considerar bloqueios não desejados, é mais fácil de entender e provar que seu código está correto.

Iterlocked.Exchange() não é apenas atômico, ele também cuida da visibilidade da memory:

As seguintes funções de synchronization usam as barreiras apropriadas para garantir a ordem da memory:

Funções que entram ou saem de seções críticas

Funções que sinalizam objects de synchronization

Funções de espera

Funções interligadas

Problemas de synchronization e multiprocessador

Isso significa que, além da atomicidade, garante que:

  • Para o encadeamento que o chama:
    • Nenhum reordenamento das instruções é feito (pelo compilador, o tempo de execução ou o hardware).
  • Para todos os tópicos:
    • Nenhuma leitura na memory que acontecer antes desta instrução verá a mudança que esta instrução fez.
    • Todas as leituras após esta instrução verão a alteração feita por esta instrução.
    • Tudo grava na memory depois que esta instrução ocorrerá depois que essa alteração de instrução tiver atingido a memory principal (ao liberar essa alteração de instrução para a memory principal quando ela estiver concluída e não permitir que o hardware dispense seu tempo de ativação).