Frage

Ich kam quer durch das auf dem Mike Ash „Pflege und Fütterung von Singletons“ und war ein wenig von seinem Kommentar puzzeled:

  

Dieser Code ist etwas langsam, aber.   eine Sperre zu nehmen ist etwas teuer.   es schmerzhafter zu machen, ist die Tatsache   dass die überwiegende Mehrheit der Zeit,   das Schloss ist sinnlos. Das Schloss ist   nur dann benötigt, wenn foo ist gleich Null, die   im Grunde geschieht nur einmal. Nach dem   Singletons initialisiert wird, die Notwendigkeit   das Schloss ist weg, aber das Schloss selbst   bleibt.

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

Meine Frage ist, und es besteht kein Zweifel einen guten Grund dafür, aber warum können Sie nicht (siehe unten) schreiben, um die Sperre zu begrenzen, wenn foo ist gleich Null?

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

prost gary

War es hilfreich?

Lösung

Da der Test auf eine Race-Bedingung unterliegt. Zwei verschiedene Threads könnte unabhängig testen, dass foo nil ist, und dann (sequentiell) erstellen separate Instanzen. Dies kann in der modifizierten Version geschehen, wenn ein Thread den Test durchführt, während die anderen noch in +[Foo alloc] oder -[Foo init] ist, aber noch nicht festgelegt foo.

By the way, ich würde es nicht tun, auf diese Weise überhaupt. Schauen Sie sich die dispatch_once()-Funktion, die Sie garantieren können, dass ein Block nur ausgeführt wird, je einmal während Ihrer App Lebensdauer (vorausgesetzt, Sie haben GCD auf der Plattform Sie Targeting).

Andere Tipps

Dies ist der double "Optimierung" checked sperren. Wie dokumentiert überall ist dies nicht sicher. Auch wenn es von einer Compiler-Optimierung nicht besiegt ist, wird es die Art und Weise Speicher auf moderne Maschinen arbeiten besiegt werden, es sei denn, Sie irgendeine Art von Zaun / Barrieren verwenden.

Mike Ash zeigt auch die richtige Lösung mit volatile und OSMemoryBarrier();.

Das Problem ist, dass, wenn ein Thread foo = [[self alloc] init]; führt es keine Garantie dafür gibt, dass, wenn ein anderer Thread den gesamten Speicher foo != 0 sieht durch init ausgeführt schreibt, ist sichtbar zu.

Siehe auch DCL und C ++ und DCL und java für weitere Details.

In Ihrer Version die Prüfung für !foo auf mehrere Threads gleichzeitig werden auftreten könnten, so dass zwei Threads in den alloc Block zu springen, ein Warten auf die andere, bevor die Zuweisung einer anderen Instanz zu beenden.

können Sie optimieren, indem nur die Sperre zu nehmen, wenn foo == null, aber nach, dass Sie wieder testen müssen (innerhalb der @synchronized) Bedingungen zum Schutz vor Rennen.

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

Der beste Weg, wenn Sie Grand Central Dispatch

haben
+ (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];
 }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top