1932 votes

Quelle est la différence entre les attributs atomiques et non atomiques ?

Qu'est-ce que atomic y nonatomic dans les déclarations de propriété ?

@property(nonatomic, retain) UITextField *userName;
@property(atomic, retain) UITextField *userName;
@property(retain) UITextField *userName;

Quelle est la différence opérationnelle entre les trois ?

1805voto

bbum Points 124887

Les deux derniers sont identiques ; "atomique" est le comportement par défaut ( notez qu'il ne s'agit pas d'un mot-clé ; il est spécifié uniquement par l'absence de nonatomic -- atomic a été ajouté comme mot-clé dans les versions récentes de llvm/clang).

En supposant que vous êtes @synthétisant les implémentations de méthodes, atomique vs. non-atomique change le code généré. Si vous écrivez vos propres setter/getters, atomic/nonatomic/retain/assign/copy ne sont que des conseils. (Note : @synthesize est maintenant le comportement par défaut dans les versions récentes de LLVM. Il n'y a pas non plus besoin de déclarer des variables d'instance ; elles seront également synthétisées automatiquement et auront une propriété _ précédés de leur nom pour éviter tout accès direct accidentel).

Avec "atomique", le setter/getter synthétisé assurera qu'un ensemble du site est toujours retournée par le getter ou définie par le setter, quelle que soit l'activité du setter sur un autre thread. C'est-à-dire que si le thread A est au milieu du getter alors que le thread B appelle le setter, une valeur réelle viable -- un objet autoreleased, très probablement -- sera retournée à l'appelant en A.

En nonatomic aucune garantie de ce type n'est donnée. Ainsi, nonatomic est considérablement plus rapide que "atomique".

Que signifie "atomique" ? no n'offre aucune garantie quant à la sécurité des fils. Si le thread A appelle le getter en même temps que les threads B et C appellent le setter avec des valeurs différentes, le thread A peut obtenir n'importe laquelle des trois valeurs retournées - celle qui précède l'appel des setters ou l'une ou l'autre des valeurs passées dans les setters de B et C. De même, l'objet peut se retrouver avec la valeur de B ou C, sans qu'il soit possible de le dire.

L'intégrité des données, l'un des principaux défis de la programmation multithread, est assurée par d'autres moyens.

Ajoutons à cela :

atomicity d'une seule propriété ne peut pas non plus garantir la sécurité des threads lorsque plusieurs propriétés dépendantes sont en jeu.

Pensez-y :

 @property(atomic, copy) NSString *firstName;
 @property(atomic, copy) NSString *lastName;
 @property(readonly, atomic, copy) NSString *fullName;

Dans ce cas, le thread A pourrait renommer l'objet en appelant setFirstName: et ensuite appeler setLastName: . Pendant ce temps, le fil B peut appeler fullName entre les deux appels du fil A et recevra le nouveau prénom associé à l'ancien nom de famille.

Pour y remédier, vous avez besoin d'un modèle transactionnel . C'est-à-dire un autre type de synchronisation et/ou d'exclusion qui permet d'exclure l'accès à fullName pendant que les propriétés dépendantes sont mises à jour.

22 votes

Étant donné que tout code thread-safe effectuera son propre verrouillage, etc., quand voudriez-vous utiliser des accesseurs de propriétés atomiques ? J'ai du mal à trouver un bon exemple.

3 votes

Si vous avez un accesseur qui va lire une structure (ou un objet) à partir de plusieurs threads et que votre code est renforcé contre "est-ce vraiment l'état actuel", c'est utile. Par exemple, si vous lisez, disons, une structure qui a quelques entrées résumant l'état actuel, l'atomique peut permettre une lecture sûre.

1 votes

Pas grave, j'ai réussi à trouver la réponse dans un commentaire ici : stackoverflow.com/a/5047416/855738 fait par bbum : "Plus précisément, puisque le mécanisme de verrouillage pour mettre en œuvre l'atomique n'est pas exposé, il n'y a aucun moyen d'implémenter manuellement juste le setter ou juste le getter et d'assurer l'atomicité avec un setter/getter atomique @synthétisé."

365voto

Louis Gerbarg Points 33025

Ceci est expliqué dans le document d'Apple documentation mais vous trouverez ci-dessous quelques exemples de ce qui se passe réellement.

Notez qu'il n'y a pas de mot clé "atomique", si vous ne spécifiez pas "nonatomique", alors la propriété est atomique, mais spécifier "atomique" explicitement entraînera une erreur.

Si vous ne spécifiez pas "nonatomique", la propriété est alors atomique, mais vous pouvez toujours spécifier "atomique" explicitement dans les versions récentes si vous le souhaitez.

//@property(nonatomic, retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}

La variante atomique est un peu plus compliquée :

//@property(retain) UITextField *userName;
//Generates roughly

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName_ retain];
      [userName release];
      userName = userName_;
    }
}

En fait, la version atomique doit prendre un verrou afin de garantir la sécurité des threads, et augmente également le nombre de références sur l'objet (et le nombre d'autorelease pour l'équilibrer) afin de garantir l'existence de l'objet pour l'appelant, sinon il y a une condition de course potentielle si un autre thread définit la valeur, ce qui fait tomber le nombre de références à 0.

Il existe en fait un grand nombre de variantes différentes de la façon dont ces choses fonctionnent, selon que les propriétés sont des valeurs scalaires ou des objets, et selon la façon dont les fonctions retain, copy, readonly, nonatomic, etc. interagissent. En général, les synthétiseurs de propriétés savent simplement comment faire la "bonne chose" pour toutes les combinaisons.

8 votes

@Louis Gerbarg : Je crois que votre version du setter (non atomique, retain) ne fonctionnera pas correctement si vous essayez d'assigner le même objet (c'est-à-dire : userName == userName_).

5 votes

Votre code est légèrement trompeur ; il y a pas de garantie sur les getters/setters atomiques qui sont synchronisés. C'est critique, @property (assign) id delegate; n'est synchronisé sur rien (iOS SDK GCC 4.2 ARM -Os ), ce qui signifie qu'il y a une course entre [self.delegate delegateMethod:self]; y foo.delegate = nil; self.foo = nil; [super dealloc]; . Voir stackoverflow.com/questions/917884/

0 votes

@fyolnish Je ne suis pas sûr de ce que _val / val sont, mais non, pas vraiment. Le getter d'un objet atomique copy / retain doit s'assurer qu'elle ne renvoie pas un objet dont le refcount devient nul parce que le setter est appelé dans un autre thread, ce qui signifie essentiellement qu'elle doit lire l'ivar, le conserver tout en s'assurant que le setter ne l'a pas écrasé et libéré, puis le libérer automatiquement pour équilibrer la conservation. Cela signifie essentiellement les deux le getter et le setter doivent utiliser un verrou (si la disposition de la mémoire était fixée, cela devrait être possible avec des instructions CAS2 ; hélas ! -retain est un appel de méthode).

174voto

raw3d Points 833

Atomique

  • est le comportement par défaut
  • s'assurera que le processus actuel est terminé par le CPU, avant qu'un autre processus n'accède à la variable
  • n'est pas rapide, car elle assure que le processus est entièrement terminé

Non-Atomique

  • n'est PAS le comportement par défaut
  • plus rapide (pour le code synthétisé, c'est-à-dire pour les variables créées à l'aide de @property et @synthesize)
  • non sécurisé par des fils
  • peut entraîner un comportement inattendu, lorsque deux processus différents accèdent à la même variable au même moment.

143voto

La meilleure façon de comprendre la différence est d'utiliser l'exemple suivant.

Supposons qu'il y ait une propriété atomique de type chaîne de caractères appelée "nom", et que l'on appelle [self setName:@"A"] à partir du fil A, appeler [self setName:@"B"] depuis le fil B, et appeler [self name] du thread C, toutes les opérations sur les différents threads seront effectuées en série, ce qui signifie que si un thread exécute un setter ou un getter, les autres threads attendront.

Cela rend la propriété "nom" sûre en lecture/écriture, mais si un autre thread, D, appelle [name release] simultanément, cette opération peut provoquer un crash car aucun appel de setter/getter n'est impliqué ici. Ce qui signifie qu'un objet est sûr en lecture/écriture (ATOMIC), mais pas en thread car d'autres threads peuvent envoyer simultanément n'importe quel type de message à l'objet. Le développeur doit s'assurer de la sécurité thread pour de tels objets.

Si la propriété "nom" était non atomique, alors tous les fils de l'exemple ci-dessus - A, B, C et D - s'exécuteront simultanément, produisant un résultat imprévisible. En cas d'atomicité, l'un ou l'autre de A, B ou C s'exécutera en premier, mais D peut toujours s'exécuter en parallèle.

120voto

justin Points 72871

La syntaxe et la sémantique sont déjà bien définies par d'autres excellentes réponses à cette question. Parce que exécution y performance ne sont pas bien détaillées, je vais ajouter ma réponse.

Quelle est la différence fonctionnelle entre ces trois-là ?

J'ai toujours considéré l'atomique comme un défaut assez curieux. Au niveau d'abstraction auquel nous travaillons, l'utilisation des propriétés atomiques d'une classe comme moyen d'obtenir une sécurité filaire à 100% est un cas limite. Pour des programmes multithreads vraiment corrects, l'intervention du programmeur est presque certainement nécessaire. En attendant, les caractéristiques de performance et l'exécution n'ont pas encore été détaillées en profondeur. Ayant écrit quelques programmes fortement multithreadés au cours des années, j'avais déclaré mes propriétés comme étant nonatomic tout le temps parce que l'atome n'avait aucun sens. Pendant la discussion des détails des propriétés atomiques et non atomiques cette question j'ai fait un peu de profilage et j'ai rencontré des résultats curieux.

Exécution

Ok. La première chose que je voudrais clarifier est que l'implémentation du verrouillage est définie par l'implémentation et abstraite. Louis utilise @synchronized(self) dans son exemple - j'ai constaté que c'était une source fréquente de confusion. L'implémentation ne en fait utiliser @synchronized(self) il utilise le niveau de l'objet verrous tournants . L'illustration de Louis est bonne pour une illustration de haut niveau utilisant des constructions qui nous sont toutes familières, mais il est important de savoir qu'elle n'utilise pas @synchronized(self) .

Une autre différence est que les propriétés atomiques conservent/libèrent le cycle de vos objets dans le getter.

Performance

Voici la partie intéressante : La performance en utilisant les accès atomiques aux propriétés dans incontesté (par exemple, en mode single-thread) peuvent être vraiment très rapides dans certains cas. Dans des cas moins qu'idéaux, l'utilisation d'accès atomiques peut coûter plus de 20 fois l'overhead de nonatomic . Alors que le Contesté en utilisant 7 threads était 44 fois plus lent pour la structure à trois octets (2,2 GHz). Core i7 Quad Core, x86_64). La struct à trois octets est un exemple de propriété très lente.

Remarque intéressante : les accesseurs définis par l'utilisateur de la structure à trois octets étaient 52 fois plus rapides que les accesseurs atomiques synthétisés, soit 84 % de la vitesse des accesseurs non atomiques synthétisés.

Les objets dans les affaires contestées peuvent également dépasser 50 fois.

En raison du nombre d'optimisations et de variations dans les implémentations, il est assez difficile de mesurer les impacts réels dans ces contextes. Vous entendrez souvent quelque chose comme "Faites confiance, sauf si vous profilez et constatez que cela pose un problème". En raison du niveau d'abstraction, il est en fait assez difficile de mesurer l'impact réel. Le calcul des coûts réels à partir des profils peut prendre beaucoup de temps et, en raison des abstractions, être assez imprécis. De même, l'ARC et le MRC peuvent faire une grande différence.

Alors prenons du recul, no en se concentrant sur l'implémentation des accès aux propriétés, nous inclurons les suspects habituels tels que objc_msgSend et examinons quelques résultats de haut niveau dans le monde réel pour de nombreux appels à un fichier de type NSString dans incontesté cas (valeurs en secondes) :

  • MRC | non atomique | récupérateurs implémentés manuellement : 2
  • MRC | nonatomique | getter synthétisé : 7
  • MRC | atomique | getter synthétisé : 47
  • ARC | nonatomique | getter synthétisé : 38 (note : l'ARC ajoute un cycle de comptage des références ici)
  • ARC | atomique | getter synthétisé : 47

Comme vous l'avez probablement deviné, l'activité de comptage des références/le cyclisme est un facteur important avec les atomiques et les sous ARC. On constate également des différences plus importantes dans les cas contestés.

Bien que je prête une attention particulière à la performance, je dis toujours La sémantique d'abord ! . Pendant ce temps, la performance est une faible priorité pour de nombreux projets. Cependant, connaître les détails d'exécution et les coûts des technologies que vous utilisez ne fait certainement pas de mal. Vous devez utiliser la bonne technologie en fonction de vos besoins, de vos objectifs et de vos capacités. Nous espérons que cela vous épargnera quelques heures de comparaisons et vous aidera à prendre une décision plus éclairée lors de la conception de vos programmes.

0 votes

MRC | atomique | getter synthétisé : 47 ARC | atomique | synthétisé getter : 47 Qu'est-ce qui les rend identiques ? ARC ne devrait-il pas avoir plus de surcharge ?

2 votes

Donc si les propriétés atomiques sont mauvaises, pourquoi sont-elles par défaut ? Pour augmenter le code passe-partout ?

0 votes

@LearnCocos2D Je viens de tester sur 10.8.5 sur la même machine, en ciblant 10.8, pour le cas simple threadé non contesté avec une NSString qui n'est pas immortel : -ARC atomic (BASELINE): 100% -ARC nonatomic, synthesised: 94% -ARC nonatomic, user defined: 86% -MRC nonatomic, user defined: 5% -MRC nonatomic, synthesised: 19% -MRC atomic: 102% -- les résultats sont un peu différents aujourd'hui. Je ne faisais pas de @synchronized comparaisons. @synchronized est sémantiquement différent, et je ne le considère pas comme un bon outil si vous avez des programmes concurrents non triviaux. si vous avez besoin de vitesse, évitez @synchronized .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X