Nach dem Lesen von Apples Dokumentation , versuche ich Atomizität oder Nichtatomizität einer Eigenschaft in Objective-C nachzuweisen. Um dies zu tun, erstelle ich eine Klasse Person, die Vor- und Nachnamen hat.
Person.h
%Vor%Person.m
%Vor%In einer anderen Klasse, hier mein AppDelegate, habe ich eine nichtatomare Eigenschaft, die eine Instanz von Person ist.
%Vor%In der Implementierungsdatei erstelle ich drei gleichzeitige Warteschlangen. In der ersten Warteschlange lese ich die Eigenschaft, in zwei anderen Warteschlangen schreibe ich verschiedene Werte von Person.
Nach meinem Verständnis könnte ich Bob Frost oder Jack Sponge in meinem Protokoll ausgeben, da ich meine Eigenschaft als nichtatomisch deklariert habe. Aber das ist nicht passiert. Ich verstehe nicht warum. Fehle ich etwas oder verstehe etwas falsch?
%Vor%Mit nicht-atomaren Eigenschaften ist die Möglichkeit von partiellen Schreibvorgängen möglich, aber keineswegs sicher.
In Ihrer Personenklasse können Sie den ersten und letzten Namen nur in der init-Methode festlegen und dann den Vornamen und dann den Nachnamen unmittelbar danach festlegen. Das Setzen des Vornamens und des Nachnamens erfolgt SEHR nahe beieinander, mit wenig Chance für einen anderen Thread, Dinge zwischen den Operationen zu vermasseln.
Außerdem erstellen Sie Ihre Person-Objekte im Hauptthread, bevor Sie gleichzeitig Operationen ausführen. Zu dem Zeitpunkt, zu dem der aktuelle Code ausgeführt wird, sind die Objekte bereits vorhanden und Sie ändern ihre Namenswerte nicht mehr, sodass keine Race Condition oder ein partielles Schreiben mit Namenswerten möglich ist. Sie ändern einfach self.p zwischen 2 Objekten, die sich nicht ändern, sobald sie erstellt wurden.
Was gesagt wird, was unvorhersehbar an Ihrem Code ist, ist, welches Personenobjekt in self.p zu irgendeinem Zeitpunkt sein wird. Sie sollten sehen, dass die angezeigten Werte zwischen Bob Sponge und Jack Frost auf unvorhersehbare Weise wechseln.
Ein besserer Test wäre etwa so:
(Angenommen, die Werte x1 und x2 jedes TestObjects sollten immer gleich bleiben.)
%Vor%Und dann Code wie folgt:
%Vor%Im obigen Code erstellt jede Warteschlange eine Reihe zufälliger Werte und setzt die x1- und x2-Eigenschaften einiger Objekte auf diese zufälligen Werte in einer sich wiederholenden Schleife. Es verzögert sich um ein kleines zufälliges Intervall zwischen dem Festlegen der x1- und x2-Eigenschaft jedes Objekts. Diese Verzögerung simuliert eine Hintergrundaufgabe, die eine gewisse Zeit benötigt, um die Arbeit zu beenden, die atomar sein sollte. Es führt auch ein Fenster ein, in dem ein anderer Thread den zweiten Wert ändern kann, bevor der aktuelle Thread den zweiten Wert festlegen kann.
Wenn Sie den obigen Code ausführen, werden Sie mit ziemlicher Sicherheit feststellen, dass die x1- und x2-Werte von thing1 und ding2 manchmal unterschiedlich sind.
Der obige Code würde nicht durch atomare Eigenschaften verbessert werden. Sie müssten eine Sperre zwischen dem Festlegen der x1- und x2-Eigenschaft jedes Objekts (möglicherweise mithilfe der @synchronized
-Direktive) einrichten.
(Beachten Sie, dass ich den obigen Code im Forum-Editor zusammengeboxt habe. Ich habe nicht versucht, ihn zu kompilieren, geschweige denn zu debuggen. Es gibt zweifellos ein paar Tippfehler.)
(Hinweis 2 an die Person, die meinen Code bearbeitet hat: Die Formatierung des Codes ist eine Frage des Stils und des persönlichen Geschmacks. Ich verwende eine Variante von "Allman-Einzug". Ich schätze die Tippfehlerkorrekturen, aber ich verachte den K & R-Stil-Einzug Setzen Sie Ihren Stil nicht auf meinen Code ein.
Eine Eigenschaft mit atomic
bedeutet, dass alle Aktionen, die von einem Lesevorgang ausgeführt werden, und alle Aktionen, die von einem Schreibvorgang ausgeführt werden, atomar ausgeführt werden. (Dies ist völlig unabhängig von der Konsistenz zwischen zwei separaten -Eigenschaften, wie in Ihrem Beispiel, die nicht einfach durch Hinzufügen von (atomic)
erreicht werden kann.)
Dies ist besonders wichtig in zwei Fällen:
Für Objektzeiger die impliziten Operationen [_property release]; [newValue retain]; _property = newValue
, die ARC ausführt, wenn Sie einen neuen Wert speichern, und die implizite value = _property; [value retain];
, die beim Laden des Werts auftritt.
Große Datentypen, deren tatsächliche Werte nicht atomar geladen / gespeichert werden können, unabhängig von der Retain / Release-Semantik.
Hier ist ein Beispiel, das beide möglichen Probleme veranschaulicht:
%Vor% Mit nonatomic
für die Objekteigenschaft erhalten Sie gelegentlich Abstürze von EXC_BAD_ACCESS und protokollieren Nachrichten wie folgt:
AtomicTest [2172: 57275] Letztes Objekt: & lt; NSObject: 0x100c04a00 & gt; objc [2172]: NSObject-Objekt 0x100c04a00 wurde beim Freigeben bereits freigegeben; break auf objc_overrelease_during_dealloc_error zu debuggen
Und für die Data-Struktur wird die Assertion gelegentlich fehlschlagen:
AtomicTest [2240: 59304] *** Assertionsfehler in - [Producer logStatus], main.m: 58
AtomicTest [2240: 59304] *** Beenden der App aufgrund der nicht abgefangenen Ausnahme 'NSInternalInconsistencyException', Grund: 'WRONG: 55937112 ^ 2! = 3128960610774769'
(Beachten Sie, dass der Wert von xSquared
, 3128960610774769, tatsächlich 55937113 2 statt 55937112 2 ist.)
Wenn Sie die Eigenschaften (atomic)
anstatt (nonatomic)
machen, vermeiden Sie diese beiden Probleme auf Kosten einer etwas langsameren Ausführung.
Randnotiz: Das selbe Problem tritt auch in Swift auf, weil keine atomaren Eigenschaften bekannt sind:
%Vor%Soweit ich das verstanden habe, könnte ich Bob Frost oder Jack Sponge in meinem Protokoll ausgeben lassen, da ich meine Eigenschaft als nichtatomisch deklariert habe. Aber das ist nicht passiert. Ich verstehe nicht warum. Fehle ich etwas oder verstehe etwas falsch?
Wenn Sie die Race Condition ausgelöst haben, ist dies nicht der Fall. Was mit Sicherheit passieren würde ist, dass Sie zusammenstoßen würden oder Sie würden etwas wirklich überraschend bekommen.
Atomic bedeutet, dass Sie immer einen konsistenten Wert erhalten, mit dem ich "einen Wert meinen, den Sie tatsächlich in die Eigenschaft setzen". Ohne Atomicy ist es möglich, einen Wert zurück zu bekommen, der nicht der any -Thread ist. Betrachten Sie dieses Programm, das gegen eine 32-Bit-Architektur kompiliert werden muss (was auch bedeutet, dass ARC deaktiviert sein muss und Sie Ihre Ivars deklarieren müssen, um dies auf dem Mac zu ermöglichen; oder Sie könnten dies auf einem 32-Bit-iPhone testen) .
%Vor%Wenn Sie das für mehr als ein paar Sekunden ausführen, erhalten Sie viele Nachrichten wie:
%Vor% Sie werden feststellen, dass weder 4294967295 noch 9223372032559808512 irgendwo im Programm erscheinen. Wie erscheinen sie in der Ausgabe? Weil ich eine 64-Bit-Nummer mit 32-Bit-Code schreibe. Es gibt keinen einzelnen Maschinenbefehl, der alle 64 Bits gleichzeitig schreibt. Die erste Hälfte wird geschrieben, dann die andere Hälfte. Wenn eine andere Warteschlange zur gleichen Zeit schreibt, können Sie mit den oberen 32 Bits von einem Schreiben und den unteren 32 Bits von dem anderen enden. atomic
verhindert dies, indem der Speicher gesperrt wird, bis alle Wörter geschrieben sind.
Bei Objekten kann ein anderes Problem auftreten. Es war vor ARC besonders problematisch, kann aber immer noch passieren. Betrachten Sie den folgenden sehr häufigen ObjC-1-Code (d. H. Vor Eigenschaften):
%Vor% Dies war eine sehr übliche Art, Accessoren zu schreiben. Handle retain-new / release-alt während des set. Gib einfach den Balkenzeiger während des get zurück. Dies ist im Grunde die Implementierung von nonatomic
heute. Das Problem ist, dass die Speicherverwaltung nicht Thread-sicher ist. Überlegen Sie, ob Sie [_something release]
nur für einen Thread und dann für einen anderen Thread den Getter aufgerufen haben. Sie erhalten den alten Wert von _something
, der bereits freigegeben und möglicherweise bereits freigegeben wurde. Vielleicht sehen Sie sich also einen ungültigen Speicher an und Sie stürzen ab.
Eine gebräuchliche Lösung war der Retainer / Autorelease-Getter:
%Vor% Dies stellte sicher, dass das, worauf _something
hinwies, mindestens bis zum Ende des aktuellen Autorelease-Pools existierte (wenn Sie es darüber hinaus wollten, lag es in Ihrer Verantwortung, es trotzdem zu behalten). Das ist ein bisschen langsamer als der triviale Getter. atomic
behebt dieses Problem auch, indem sichergestellt wird, dass kein Fang in die Mitte der Einstellung gelangt.
Alles, was gesagt wurde, kann in einigen Fällen nützlich sein, fast immer, wenn Sie auf Daten in mehreren Warteschlangen zugreifen, ist atomic
nicht ausreichend und sowieso langsam (zumindest war es früher; habe keine aktuellen Versionen erstellt, da ich nie atomic
) verwende. Wenn alles, was Sie wollen, ist Single-Eigenschaft atomicy, ein GCD-Accessor ist normalerweise besser. Wenn Sie eine vollständig atomare Transaktion benötigen (was Sie oft tun), dann kann der GCD-Accessor auch darauf leicht angepasst werden.
Die wahrscheinlich beste Diskussion darüber ist der Blog-Beitrag von bbum: Ссылка . Die kurze Antwort ist, dass es sehr selten ist, dass atomic
tatsächlich hilfreich ist. Wenn Sie denken, dass Sie atomic
benötigen, brauchen Sie in der Regel mehr, als es Ihnen zur Verfügung steht, und können es normalerweise mit GCD-Accessoren billiger bekommen.
% atomic
zum Standard zu machen, war einer der großen Fehler, die Apple in ObjC2 gemacht hat.
Tags und Links objective-c ios macos atomic