Domanda

Sono venuto incontrato questo sulla "cura e l'alimentazione dei single" Mike Ash ed era un po Puzzeled dal suo commento:

  

Questo codice è un po 'lento, però.   Prendendo un blocco è un po 'costoso.   Il che rende più doloroso è il fatto   che la stragrande maggioranza del tempo,   il blocco è inutile. La serratura è   necessaria solo quando foo è trascurabili,   fondamentalmente accade solo una volta. Dopo il   Singleton viene inizializzato, la necessità di   il blocco è andato, ma la serratura stessa   rimane.

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

La mia domanda è, e non v'è alcun dubbio una buona ragione per questo, ma perché non si può scrivere (vedi sotto) per limitare la serratura quando foo è pari a zero?

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

applausi Gary

È stato utile?

Soluzione

Perché allora il test è soggetto a una condizione di competizione. Due fili diversi potrebbero testare autonomamente che foo è nil, e poi (sequenziale) creare istanze separate. Questo può accadere nella vostra versione modificata quando un thread esegue il test, mentre l'altro è ancora all'interno +[Foo alloc] o -[Foo init], ma non ha ancora fissato foo.

A proposito, io non lo farei in questo modo a tutti. Controlla la funzione dispatch_once(), che consente di garantire che un blocco è sempre e solo eseguito una volta durante la vita di tua app (ammesso che abbiate GCD sulla piattaforma che stai targeting).

Altri suggerimenti

Questa è chiamata la doppia checked bloccaggio "ottimizzazione" . Come documentato in tutto il mondo questo non è sicuro. Anche se non è sconfitto da un ottimizzazione del compilatore, che sarà sconfitto la memoria modo in cui funziona su macchine moderne, a meno che non si utilizza un qualche tipo di recinzione / barriere.

Mike Ash mostra anche la soluzione corretta usando volatile e OSMemoryBarrier();.

Il problema è che quando un thread esegue foo = [[self alloc] init]; v'è alcuna garanzia che quando un altro thread vede foo != 0 tutta la memoria scrive eseguita da init è visibile anche.

DCL e C ++ e DCL e java per ulteriori dettagli.

Nella versione del controllo per !foo potrebbe avvenire su più thread contemporaneamente, consentendo a due fili di saltare nel blocco alloc, uno in attesa che l'altro traguardo prima ripartizione un'altra istanza.

È possibile ottimizzare esclusivamente la serratura se foo == pari a zero, ma dopo che è necessario testare ancora una volta (all'interno della @synchronized) per premunirsi contro condizioni di gara.

+ (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;
}

Il modo migliore se si dispone di grande spedizione centrale

+ (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];
 }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top