Pergunta

Eu gostaria de codificar um pequeno programa que ilustra visualmente o comportamento do volatile palavra -chave. Idealmente, deve ser um programa que execute acesso simultâneo a um campo estático não volátil e que obtém comportamento incorreto por causa disso.

Adicionar a palavra -chave volátil no mesmo programa deve corrigir o problema.

É algo que eu não consegui alcançar. Mesmo tentando várias vezes, permitindo a otimização, etc., sempre recebo um comportamento correto sem a palavra -chave 'volátil'.

Você tem alguma ideia sobre este tópico? Você sabe como simular esse problema em um aplicativo de demonstração simples? Isso depende do hardware?

Foi útil?

Solução

Eu consegui um exemplo de trabalho!

A idéia principal recebeu do Wiki, mas com algumas mudanças para C#. O artigo do Wiki demonstra isso para o campo estático de C ++, parece que C# sempre compile solicitações para campos estáticos ... e eu faço exemplo com um não estático:

Se você executar este exemplo em Liberar modo e sem depurador (ou seja, usando Ctrl+F5) então a linha while (test.foo != 255) será otimizado para 'while (true)' e este programa nunca retornar. Mas depois de adicionar volatile palavra -chave, você sempre fica 'ok'.

class Test
{
    /*volatile*/ int foo;

    static void Main()
    {
        var test = new Test();

        new Thread(delegate() { Thread.Sleep(500); test.foo = 255; }).Start();

        while (test.foo != 255) ;
        Console.WriteLine("OK");
    }
}

Outras dicas

Sim, é dependente de hardware (é improvável que você veja o problema sem vários processadores), mas também depende da implementação. As especificações do modelo de memória na especificação CLR permitem coisas que a implementação da Microsoft do CLR não necessariamente faz. A melhor documentação que eu já vi na palavra -chave volátil é Esta postagem do blog de Joe Duffy. Observe que ele diz que a documentação do MSDN é "altamente enganosa".

Não é realmente uma questão de falha que aconteça quando a palavra -chave 'volátil' não é especificada, mais do que um erro pode acontecer quando não for especificado. Geralmente você vai saber quando esse é o caso melhor que o compilador!

A maneira mais fácil de pensar sobre isso seria que o compilador pudesse, se quisesse, embutir certos valores. Ao marcar o valor como volátil, você está dizendo a si mesmo e ao compilador que o valor pode realmente mudar (mesmo que o compilador não pense assim). Isso significa que o compilador não deve valores em linha, manter o cache ou ler o valor mais cedo (na tentativa de otimizar).

Esse comportamento não é realmente a mesma palavra -chave que no C ++.

MSDN tem uma breve descrição aqui. Aqui está talvez um post mais detalhado sobre os assuntos de Volatilidade, atomicidade e intertravamento

É difícil demonstrar em C#, pois o código é abstraído por uma máquina virtual, portanto, em uma implementação desta máquina, funciona corretamente sem volátil, enquanto pode falhar em outra.

A Wikipedia tem um bom exemplo de como demonstrá -lo em C, no entanto.

O mesmo pode acontecer em C# se o compilador JIT decidir que o valor da variável não pode mudar de qualquer maneira e, portanto, criar código de máquina que nem sequer o verifica. Se agora outro segmento estava mudando o valor, seu primeiro thread ainda pode ser capturado no loop.

Outro exemplo está ocupado esperando.

Novamente, isso também pode acontecer com C#, mas depende fortemente da máquina virtual e do compilador JIT (ou intérprete, se não tiver jit ... em teoria, acho que a MS sempre usa um compilador JIT e também o mono usa um; mas você poderá desativá -lo manualmente).

Aqui está minha contribuição para a compreensão coletiva desse comportamento ... não é muito, apenas uma demonstração (com base na demo do XKIP) que mostra o comportamento de um versículos voláteis de um valor não volátil (ou seja, "normal") int, lado a lado -Side, no mesmo programa ... que era o que eu estava procurando quando encontrei este tópico.

using System;
using System.Threading;

namespace VolatileTest
{
  class VolatileTest 
  {
    private volatile int _volatileInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _volatileInt = 1; }).Start();
      while ( _volatileInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_volatileInt="+_volatileInt);
    }
  }

  class NormalTest 
  {
    private int _normalInt;
    public void Run() {
      new Thread(delegate() { Thread.Sleep(500); _normalInt = 1; }).Start();
      // NOTE: Program hangs here in Release mode only (not Debug mode).
      // See: http://stackoverflow.com/questions/133270/illustrating-usage-of-the-volatile-keyword-in-c-sharp
      // for an explanation of why. The short answer is because the
      // compiler optimisation caches _normalInt on a register, so
      // it never re-reads the value of the _normalInt variable, so
      // it never sees the modified value. Ergo: while ( true )!!!!
      while ( _normalInt != 1 ) 
        ; // Do nothing
      Console.WriteLine("_normalInt="+_normalInt);
    }
  }

  class Program
  {
    static void Main() {
#if DEBUG
      Console.WriteLine("You must run this program in Release mode to reproduce the problem!");
#endif
      new VolatileTest().Run();
      Console.WriteLine("This program will now hang!");
      new NormalTest().Run();
    }

  }
}

Existem algumas explicações sucintas realmente excelentes acima, bem como algumas grandes referências. Obrigado a todos por me ajudar volatile (pelo menos o suficiente para saber não confiar volatile onde meu primeiro instinto estava lock isto).

Saúde, e obrigado por todo o peixe. Keith.


PS: Eu estaria muito interessado em uma demonstração da solicitação original, que era: "Eu gostaria de ver uma estático volátil int comportando -se corretamente onde uma estático int se comporta mal.

Eu tentei e falhei nesse desafio. (Na verdade, desisti muito rapidamente ;-). Em tudo que tentei com vars estáticos, eles se comportam "corretamente", independentemente de eles volátil ... e eu adoraria uma explicação de por que esse é o caso, se é que é o caso ... é que o compilador não cache o valores de vars estáticos em registros (ou seja, cache um referência a aquele endereço de heap em vez disso)?

Não, isso não é uma nova pergunta ... é uma tentativa de Stear the Community de volta para a pergunta original.

Me deparei com o seguinte texto de Joe Albahari, que me ajudou muito.

Peguei um exemplo do texto acima que alterei um pouco, criando um campo volátil estático. Quando você remove o volatile palavra -chave O programa bloqueará indefinidamente. Execute este exemplo em Liberar modo.

class Program
{
    public static volatile bool complete = false;

    private static void Main()
    {           
        var t = new Thread(() =>
        {
            bool toggle = false;
            while (!complete) toggle = !toggle;
        });

        t.Start();
        Thread.Sleep(1000); //let the other thread spin up
        complete = true;
        t.Join(); // Blocks indefinitely when you remove volatile
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top