Pergunta

I veio em este sobre a Mike Ash "cuidados e alimentação de singletons" e foi um pouco puzzeled pelo seu comentário:

Este código é uma espécie de lento, no entanto. Tomando um bloqueio é um pouco caro. Tornando-o mais doloroso é o fato que a grande maioria das vezes, o bloqueio é inútil. O bloqueio é só precisava quando foo é nulo, ou seja basicamente só acontece uma vez. Depois de Singleton é inicializado, a necessidade de o bloqueio se foi, mas o próprio bloqueio restos.

+(id)sharedFoo {
    static Foo *foo = nil;
    @synchronized([Foo class]) {
        if(!foo) foo = [[self alloc] init];
    }
    return foo;
}

A minha pergunta é, e não há dúvida uma boa razão para isso, mas por que você não pode escrever (veja abaixo) para limitar o bloqueio para quando foo é nulo?

+(id)sharedFoo {
    static Foo *foo = nil;
    if(!foo) {
        @synchronized([Foo class]) {
            foo = [[self alloc] init];
        }
    }
    return foo;
}

aplausos Gary

Foi útil?

Solução

Porque então o teste está sujeita a uma condição de corrida. Dois segmentos diferentes podem testar independentemente foo que é nil, e, em seguida, (sequencialmente) criar instâncias separadas. Isso pode acontecer em sua versão modificada quando um executa rosca o teste, enquanto o outro ainda está dentro +[Foo alloc] ou -[Foo init], mas ainda não tem conjunto foo.

A propósito, eu não iria fazê-lo dessa maneira em tudo. Confira a função dispatch_once(), que permite garantir que um bloco é sempre apenas executado uma vez durante a vida de seu aplicativo (supondo que você tem GCD na plataforma que você está alvejando).

Outras dicas

Este é o chamado dobro verificado bloqueio "otimização" . Conforme documentado em todos os lugares isso não é seguro. Mesmo se não é derrotado por uma otimização do compilador, ele será derrotado as obras de memória forma em máquinas modernas, a menos que você use algum tipo de cercas / barreiras.

Mike Ash mostra também a solução correta usando volatile e OSMemoryBarrier();.

A questão é que, quando se executa rosca foo = [[self alloc] init]; não há garantia de que, quando um outro segmento vê foo != 0 toda a memória escreve realizada por init é visível também.

Veja também DCL e C ++ e DCL e java para mais detalhes.

Na sua versão do cheque de !foo poderia ser ocorrendo em vários threads ao mesmo tempo, permitindo que dois threads para saltar para o bloco alloc, um espera que o outro terminar antes de alocar outra instância.

Você pode otimizar tomando somente o bloqueio se foo nil ==, mas depois que você precisa para testar novamente (dentro do @synchronized) para se proteger contra as condições de corrida.

+ (id)sharedFoo {
    static Foo *foo = nil;
    if(!foo) {
        @synchronized([Foo class]) {
            if (!foo)  // test again, in case 2 threads doing this at once
                foo = [[self alloc] init];
        }
    }
    return foo;
}

A melhor maneira se você tiver expedição Grand Central

+ (MySingleton*) instance {
 static dispatch_once_t _singletonPredicate;
 static MySingleton *_singleton = nil;

 dispatch_once(&_singletonPredicate, ^{
    _singleton = [[super allocWithZone:nil] init];
 });

 return _singleton
 }
+ (id) allocWithZone:(NSZone *)zone {
  return [self instance];
 }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top