Question

Je suis tombé sur ce sur les « soins et l'alimentation des singletons » Mike Ash et a été un peu puzzeled par son commentaire:

  

Ce code est un peu lent, cependant.   Prendre un verrou est un peu cher.   Rendre plus douloureux est le fait   que la grande majorité du temps,   la serrure est inutile. Le verrou est   requis uniquement si foo est nul, ce qui   essentiellement se produit une seule fois. Après le   singleton est initialisé, la nécessité de   le verrou est parti, mais la serrure elle-même   reste.

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

Ma question est, et il ne fait aucun doute une bonne raison pour cela, mais pourquoi ne pas pouvez-vous écrire (voir ci-dessous) afin de limiter le verrou quand foo est nul?

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

hourras gary

Était-ce utile?

La solution

Parce que le test est soumis à une condition de course. Deux fils différents peuvent tester de façon indépendante que foo est nil, puis (séquentiellement) créer des instances distinctes. Cela peut se produire dans votre version modifiée quand un thread effectue le test alors que l'autre est encore à l'intérieur +[Foo alloc] ou -[Foo init], mais n'a pas encore fixé foo.

Par ailleurs, je ne le ferais pas ça du tout. Consultez la fonction dispatch_once(), qui vous permet de garantir qu'un bloc ne jamais exécuté une fois au cours (en supposant que vous avez GCD sur la plate-forme que vous ciblez) la durée de vie de votre application.

Autres conseils

Ceci est appelé revérifié verrouillage "optimisation" . Comme indiqué partout ce n'est pas sûr. Même si ce n'est pas vaincu par une optimisation du compilateur, il sera vaincu la mémoire façon dont fonctionne sur les machines modernes, à moins que vous utilisez une sorte de clôture / barrières.

Mike Ash montre également la solution correcte à l'aide volatile et OSMemoryBarrier();.

Le problème est que lorsqu'un thread exécute foo = [[self alloc] init]; il n'y a aucune garantie que lorsqu'un autre thread voit foo != 0 toute la mémoire réalisée par init écritures est visible aussi.

Voir aussi DCL et C ++ et DCL et java pour plus de détails.

Dans votre version du chèque de !foo pourrait se produire sur plusieurs threads en même temps, ce qui permet deux fils de sauter dans le bloc alloc, une attente de l'autre pour terminer avant d'allouer une autre instance.

Vous pouvez optimiser en prenant seulement la serrure si foo == nul, mais après que vous avez besoin de tester à nouveau (dans le @synchronized) pour se prémunir contre les conditions de course.

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

La meilleure façon si vous avez grande expédition 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];
 }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top