Wie wird @synchronized in Objective-C gesperrt / entsperrt?


201

Verwendet @synchronized nicht "Sperren" und "Entsperren", um einen gegenseitigen Ausschluss zu erreichen? Wie sperrt / entsperrt es dann?

Die Ausgabe des folgenden Programms ist nur "Hello World".

@interface MyLock: NSLock<NSLocking>
@end

@implementation MyLock

- (id)init {
    return [super init];
}

- (void)lock {
    NSLog(@"before lock");
    [super lock];
    NSLog(@"after lock");
}

- (void)unlock {
    NSLog(@"before unlock");
    [super unlock];
    NSLog(@"after unlock");
}

@end


int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    MyLock *lock = [[MyLock new] autorelease];
    @synchronized(lock) {
        NSLog(@"Hello World");
    }

    [pool drain];
}


10
Sie müssen init nicht überschreiben, wenn Sie es nicht benötigen. Die Laufzeit ruft automatisch die Implementierung der Oberklasse auf, wenn Sie eine Methode nicht überschreiben.
Constantino Tsarouhas

3
Es ist wichtig zu beachten, dass der obige Code nicht synchronisiert ist. Das lockObjekt wird bei jedem Aufruf erstellt, sodass es niemals einen Fall gibt, in dem ein @synchronizedBlock einen anderen sperrt. Und das bedeutet, dass es keinen gegenseitigen Ausschluss gibt.) Natürlich führt das obige Beispiel die Operation in aus main, daher gibt es sowieso nichts auszuschließen, aber man sollte diesen Code nicht blind an eine andere Stelle kopieren.
Hot Licks

3
Nachdem ich diese SO-Seite gelesen hatte, beschloss ich, @synchronized etwas genauer zu untersuchen und einen Blog-Beitrag darüber zu schreiben. Sie können es nützlich finden: rykap.com/objective-c/2015/05/09/synchronized
rjkaplan

Antworten:


323

Die Synchronisation auf Objective-C-Sprachebene verwendet genau wie NSLocksie den Mutex . Semantisch gibt es einige kleine technische Unterschiede, aber es ist grundsätzlich richtig, sie als zwei separate Schnittstellen zu betrachten, die auf einer gemeinsamen (primitiveren) Entität implementiert sind.

Insbesondere mit a haben NSLockSie eine explizite Sperre, während mit @synchronizedIhnen eine implizite Sperre für das Objekt verknüpft ist, mit dem Sie synchronisieren. Der Vorteil der Sperre auf Sprachebene besteht darin, dass der Compiler sie versteht, damit er sich mit Scoping-Problemen befassen kann, sich mechanisch jedoch grundsätzlich gleich verhält.

Sie können sich @synchronizedein Compiler-Rewrite vorstellen:

- (NSString *)myString {
  @synchronized(self) {
    return [[myString retain] autorelease];
  }
}

verwandelt sich in:

- (NSString *)myString {
  NSString *retval = nil;
  pthread_mutex_t *self_mutex = LOOK_UP_MUTEX(self);
  pthread_mutex_lock(self_mutex);
  retval = [[myString retain] autorelease];
  pthread_mutex_unlock(self_mutex);
  return retval;
}

Das ist nicht genau richtig, da die eigentliche Transformation komplexer ist und rekursive Sperren verwendet, aber es sollte den Punkt vermitteln.


17
Sie vergessen auch die Ausnahmebehandlung, die @synchronized für Sie erledigt. Und so wie ich es verstehe, wird ein Großteil davon zur Laufzeit erledigt. Dies ermöglicht die Optimierung von nicht angeforderten Sperren usw.
Quinn Taylor

7
Wie gesagt, das tatsächlich generierte Zeug ist komplexer, aber ich hatte keine Lust, Abschnittsanweisungen zu schreiben, um die DWARF3-Abwicklungstabellen zu erstellen ;-)
Louis Gerbarg

Und ich kann dir keine Vorwürfe machen. :-) Beachten Sie auch, dass OS X das Mach-O-Format anstelle von DWARF verwendet.
Quinn Taylor

5
Niemand verwendet DWARF als Binärformat. OS X verwendet DWARF für Debug-Symbole und DWARF-Abwicklungstabellen für Null-Kosten-Ausnahmen
Louis Gerbarg

7
Als Referenz habe ich Compiler-Backends für Mac OS X geschrieben ;-)
Louis Gerbarg

40

In Objective-C @synchronizedübernimmt ein Block das Sperren und Entsperren (sowie mögliche Ausnahmen) automatisch für Sie. Die Laufzeit generiert im Wesentlichen dynamisch ein NSRecursiveLock, das dem Objekt zugeordnet ist, auf dem Sie synchronisieren. In dieser Apple-Dokumentation wird dies ausführlicher erläutert. Aus diesem Grund werden die Protokollnachrichten Ihrer NSLock-Unterklasse nicht angezeigt. Das Objekt, für das Sie synchronisieren, kann alles sein, nicht nur ein NSLock.

Grundsätzlich @synchronized (...)handelt es sich um ein Convenience-Konstrukt, das Ihren Code rationalisiert. Wie bei den meisten vereinfachenden Abstraktionen ist damit ein Overhead verbunden (stellen Sie sich dies als versteckte Kosten vor), und es ist gut, sich dessen bewusst zu sein, aber die rohe Leistung ist wahrscheinlich nicht das oberste Ziel, wenn Sie solche Konstrukte verwenden.


1
Dieser Link ist abgelaufen. Hier ist der aktualisierte Link: developer.apple.com/library/archive/documentation/Cocoa/…
Ariel Steiner

31

Tatsächlich

{
  @synchronized(self) {
    return [[myString retain] autorelease];
  }
}

verwandelt sich direkt in:

// needs #import <objc/objc-sync.h>
{
  objc_sync_enter(self)
    id retVal = [[myString retain] autorelease];
  objc_sync_exit(self);
  return retVal;
}

Diese API ist seit iOS 2.0 verfügbar und wird importiert mit ...

#import <objc/objc-sync.h>

Es bietet also keine Unterstützung für den sauberen Umgang mit ausgelösten Ausnahmen?
Dustin

Ist das irgendwo dokumentiert?
jbat100

6
Da ist eine unausgeglichene Klammer.
Potatoswatter

@Dustin tut es tatsächlich aus den Dokumenten: "Als Vorsichtsmaßnahme @synchronizedfügt der Block dem geschützten Code implizit einen Ausnahmehandler hinzu. Dieser Handler gibt den Mutex automatisch frei, falls eine Ausnahme ausgelöst wird."
Pieter

objc_sync_enter wird wahrscheinlich pthread mutex verwenden, daher ist Louis 'Transformation tiefer und korrekter.
Jack


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.