Pergunta

Então eu vi um monte de artigos agora, alegando que em C ++ dobro verificado bloqueio, comumente utilizado para prevenir vários segmentos de tentar inicializar um singleton preguiçosamente criado, está quebrado. normal duplo verificado bloqueio de código lê assim:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!instance)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;
        }

        return *instance;
    }
};

O problema, aparentemente, é a instância linha atribuindo - o compilador é livre para alocar o objeto e, em seguida, atribuir o ponteiro para ele, ou para definir o ponteiro para onde serão destinados, em seguida, alocá-lo. As últimas quebras de caso do idioma - uma thread pode alocar a memória e atribuir o ponteiro, mas não executar o construtor do singleton antes que seja posto para dormir -, em seguida, o segundo segmento vai ver que a instância não é nulo e tentar devolvê-lo , mesmo que ainda não foi construído.

viu uma sugestão usar um fio boolean local e verificar que em vez de instance. Algo parecido com isto:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;
    static boost::thread_specific_ptr<int> _sync_check;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!_sync_check.get())
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            // Any non-null value would work, we're really just using it as a
            // thread specific bool.
            _sync_check = reinterpret_cast<int*>(1);
        }

        return *instance;
    }
};

Desta forma, cada thread acaba verificar se a instância foi criada uma vez, mas pára depois disso, o que implica algum impacto na performance, mas ainda não tão ruim quanto bloqueio cada chamada. Mas o que se acabou de usar um booleano estática local:?

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static bool sync_check = false;
        static singleton* instance;

        if(!sync_check)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            sync_check = true;
        }

        return *instance;
    }
};

Por que não funciona? Mesmo se sync_check eram para ser lido por um fio quando está sendo atribuído em outro o valor de lixo ainda será diferente de zero e, portanto, verdadeira. reivindicações artigo deste Dr. Dobb que você tem que trava porque você nunca vai ganhar uma batalha com o compilador mais de reordenamento instruções. O que me faz pensar que deve não funcionar por algum motivo, mas eu não consigo descobrir o porquê. Se os requisitos de pontos de seqüência são como perder o artigo do Dr. Dobb me faz acreditar, eu não entendo por que qualquer código após o bloqueio não pode ser reordenados para ser antes do bloqueio. O que tornaria C ++ multithreading período quebrada.

Eu acho que eu podia ver o compilador de serem autorizados a sync_check especificamente reordenar a ser antes do bloqueio, porque é uma variável local (e mesmo que seja estática não estamos retornando uma referência ou ponteiro para ele) - mas, então isso poderia ainda ser resolvido, tornando-a um membro estático (efetivamente global) em seu lugar.

Assim, este trabalho ou não vai? Por quê?

Foi útil?

Solução

A sua correção não resolve nada, já que as gravações em sync_check e instância pode ser feito fora de ordem sobre a CPU. Como exemplo imaginar as duas primeiras chamadas para exemplo acontecer em aproximadamente ao mesmo tempo em duas CPUs diferentes. O primeiro segmento irá adquirir o bloqueio, inicializar o ponteiro e conjunto sync_check a verdade, nessa ordem, mas o processador pode alterar a ordem das gravações para memória. Por outro CPU então é possível que o segundo segmento para verificar sync_check, ver que é verdade, mas caso ainda não podem ser gravados na memória. Consulte Considerações Lockless programação para Xbox 360 e Microsoft Windows para mais detalhes.

A solução específica sync_check fio você menciona deve funcionar, em seguida, (supondo que você inicializar o ponteiro para 0).

Outras dicas

Há alguma leitura grande sobre isso (embora seja .net / c # orientada) aqui: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx

O que ferve para baixo é que você precisa ser capaz de dizer a CPU que não pode reordenar sua lê / escreve para esse acesso variável (desde o Pentium original, a CPU pode reordenar certas instruções se ele pensa que a lógica não seria afetado), e que ele precisa para garantir que o cache é consistente (não se esqueça que - nós devs começa a fingir que toda a memória é apenas um recurso plana, mas na realidade, cada núcleo do processador tem cache, alguns unshared (L1), alguns podem ser compartilhadas por vezes (L2)) - o seu initizlization pode escrever a RAM principal, mas outro núcleo pode ter o valor não inicializado em cache. Se você não tem nenhum semântica de concorrência, a CPU pode não saber que é cache é sujo.

Eu não sei o lado do C ++, mas em .net, você iria designar a variável como volátil, a fim de acesso proteção a ele (ou você usaria a memória de leitura / gravação métodos de barreira em System.Threading).

código que não ajudá-lo com o seu c ++ -

Como um aparte, eu li que em .NET 2.0, bloqueio duplo verificado é garantido para trabalho sem variáveis ??"voláteis" (para todos os leitores .net lá fora) .

Se você quiser ser seguro, você precisará fazer o c ++ equivalente a marcação de uma variável como volátil em c #.

"último caso breaks o idioma -. Dois segmentos podem acabar criando o singleton"

Mas se eu entender o código corretamente, o primeiro exemplo, você verificar se a instância já existe (pode ser executado por vários threads ao mesmo tempo), se isso não acontecer um get fio de para travá-lo e cria a instância - apenas um thread pode executar a criação nesse momento. Todos os outros tópicos ficar trancado para fora e vai esperar.

Uma vez que a instância é criada ea exclusão mútua é desbloqueado o próximo segmento de espera irá travar mutex, mas não vai tentar criar nova instância porque a verificação irá falhar.

Da próxima vez que a variável de instância está marcada será definido de modo nenhum thread irá tentar criar nova instância.

Eu não tenho certeza sobre o caso em que um segmento está atribuindo novo ponteiro caso para caso, enquanto outro segmento verifica a mesma variável - mas eu acredito que vai ser tratada corretamente neste caso

.

Estou faltando alguma coisa aqui?

Ok não tem certeza sobre o reordenamento das operações, mas neste caso seria alterar a lógica que eu não iria esperar que isso aconteça -. Mas não sou um especialista sobre este tema

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