Je suis arrivé à ce sujet sur le Mike Ash "Care and Nourrage de Singletons" et était un peu puzzle par son commentaire:
Ce code est un peu lent, cependant. Prendre un verrou est un peu cher. Le rendre plus douloureux est le fait que la grande majorité du temps, La serrure est inutile. Le verrou est seulement nécessaire quand foo est nul, lequel Fondamentalement ne se produit qu'une fois. Après le Singleton est initialisé, la nécessité de La serrure est partie, mais la serrure elle-même reste. p> BlockQuote>
+(id)sharedFoo { static Foo *foo = nil; if(!foo) { @synchronized([Foo class]) { foo = [[self alloc] init]; } } return foo; }
5 Réponses :
Parce que le test est soumis à une condition de course. Deux threads différents peuvent tester indépendamment que Au fait, je ne le ferais pas de cette façon. Consultez la fonction foo code> est nil code>, puis (séquentiellement) créer des instances distinctes. Cela peut arriver dans votre version modifiée lorsqu'un thread exécute le test tandis que l'autre est toujours à l'intérieur de + [foo alloc] code> ou - [foo init] code>, mais n'a pas encore défini foo code>. p>
Dispatch_once () CODE>, ce qui vous permet de garantir qu'un bloc n'est jamais exécuté une fois lors de la vie de votre application (en supposant que vous disposiez de GCD sur la plate-forme que vous ciblez). P>
C'est bien sûr vrai. Mais la meilleure solution ne serait pas de tester deux fois (à l'intérieur et b> en dehors du @Synchronisé code>). Ensuite, il n'y aurait pas de condition de race ni de pénalité de performance.
@Nikolai: Dis-moi qu'il y a une pénalité de performance après i> vous avez couru du requin. :-)
@Graham: Il ne fait aucun doute que la performance est mauvaise dans la version originale qui prend toujours la serrure. Je l'avais dans mon code et j'ai dirigé Shark i>;). En outre, Mike Ash l'a signalé dans son poteau de blog d'origine.
Bonjour Graham, pour le moment je me développe pour l'iPhone, alors je voulais vraiment juste comprendre ce qui se passait.
@Nikolai: Mike ne fonctionne pas toujours requin. Mais si la performance est mauvaise pour vous, quelle fréquence accédez-vous à ce singleton? Doit être des milliers de fois une seconde ou plus pour obtenir un coût notable.
@Graham: Dans mon cas, j'ai cherché une base de données de 800 000 enregistrements. Dans la boucle chaude, j'ai accédé à la base de données à l'aide du modèle Singleton. La recherche était deux fois plus rapide après avoir déplacé le [mydb SharedDB] code> hors de la boucle. Je suis arrivé à la conclusion que la synchronisation est vraiment lente (au moins sur la plate-forme des bras que j'utilisais) et je cherchais des alternatives. Mike Ash (et @mfazekas ci-dessous) expliqua, pourquoi le verrouillage vérifié n'est pas une bonne idée.
développeur.apple.com/Library / Mac / # Documentation / Darwin / Réserve NCE / ...
Dans votre version, la vérification de ! FOO code> pourrait se produire sur plusieurs threads en même temps, permettant à deux threads de passer dans le bloc code> alloc code>, une attente de la autre à terminer avant d'attribuer un autre instance. P>
Vous pouvez optimiser en prenant uniquement la serrure si FOO == nil, mais après cela, vous devez tester à nouveau (dans le @Synchronisé) pour se protéger des 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;
}
Ceci s'appelle le Verrouillage double coché "Optimisation" . Comme documenté partout, cela n'est pas sûr. Même si ce n'est pas vaincu par une optimisation du compilateur, il sera vaincu la manière dont la mémoire fonctionne sur des machines modernes, à moins que vous n'utilisiez une sorte de clôture / barrières. p>
Mike Ash également indique la solution correcte en utilisant Le problème est que lorsqu'un thread exécute Voir aussi DCL et C ++ et DCL et Java pour plus de détails. P> volatile code> et osmemorybarrier (); code>. P>.
foo = [[auto-alloc] init]; code> Il n'y a aucune garantie que lorsqu'un autre thread voit foo! = 0 code> tout Les écrivies de mémoire effectuées par init code> sont également visibles. P>
+1 Merci d'avoir fait cela clair. Les instructions de réorganisation et d'accès à la mémoire hors commandement sont les deux concepts que la plupart des programmeurs ne sont pas au courant.
Dispatch_Oronce est la solution réelle, utilisez simplement cela et quitter le piratage
meilleur moyen si vous avez une grande expédition Cenral
Ah OK, donc fondamentalement, vous avez besoin d'un chèque à l'intérieur du bloc @Synchronize?
C'est tout le point de la @Synchronized: Autoriser un fil à la fois pour faire le chèque.
Essayez DisPatch_Oronce () à la place: Stackoverflow.com/q/5720029/290295
Voici quelques points de repère comparant @synchronized "singleton" implémentations par rapport à Dispatch_once () (Dispatch_Onece gagne à la bonne manière) BJHOMER.BLOGSPOT.COM/2011/09/SYNCHRONISÉ-VS-DISPATCHONCE.HT ML