W jaki sposób @synchronizowane blokowanie / odblokowywanie w Objective-C?

201

Czy @synchronized nie używa „blokady” i „odblokowania” do osiągnięcia wzajemnego wykluczenia? Jak to się wtedy blokuje / odblokowuje?

Wyjście następującego programu to tylko „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];
}
David Lin
źródło
Uwaga: Powiązane z stackoverflow.com/questions/1215765
Quinn Taylor
10
Nie musisz zastępować init, jeśli go nie potrzebujesz. Środowisko wykonawcze automatycznie wywołuje implementację nadklasy, jeśli nie zastąpi się metody.
Constantino Tsarouhas,
3
Należy zauważyć, że powyższy kod nie jest zsynchronizowany. lockObiekt jest tworzony na każde wezwanie, więc nigdy nie będzie to przypadek, gdzie jeden @synchronizedzamki blokują inny. A to oznacza, że ​​nie ma wzajemnego wykluczenia.) Oczywiście powyższy przykład wykonuje operację main, więc i tak nie ma co wykluczyć, ale nie należy ślepo kopiować tego kodu w innym miejscu.
Hot Licks,
3
Po przeczytaniu tej strony SO postanowiłem trochę dokładniej zbadać @synchronized i napisać na niej post na blogu. Może się przydać: rykap.com/objective-c/2015/05/09/synchronized
rjkaplan

Odpowiedzi:

323

Synchronizacja na poziomie języka Objective-C wykorzystuje mutex, podobnie jak NSLockrobi to. Semantycznie istnieją pewne niewielkie różnice techniczne, ale zasadniczo poprawne jest myślenie o nich jako o dwóch oddzielnych interfejsach zaimplementowanych na wspólnej (bardziej prymitywnej) jednostce.

W szczególności NSLockmasz blokadę jawną, podczas gdy @synchronizedmasz ukrytą blokadę związaną z obiektem, którego używasz do synchronizacji. Zaletą blokowania na poziomie języka jest to, że kompilator to rozumie, więc może poradzić sobie z problemami z zakresu, ale mechanicznie zachowują się w zasadzie tak samo.

Możesz pomyśleć o @synchronizedprzepisaniu kompilatora:

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

przekształca się w:

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

Nie jest to do końca poprawne, ponieważ rzeczywista transformacja jest bardziej złożona i korzysta z blokad rekurencyjnych, ale powinna mieć sens.

Louis Gerbarg
źródło
17
Zapominacie również o obsłudze wyjątków, którą @synchronized robi dla was. I jak rozumiem, wiele z tego jest obsługiwanych w czasie wykonywania. Pozwala to na optymalizację niezamkniętych zamków itp.
Quinn Taylor
7
Tak jak powiedziałem, rzeczywiste generowane rzeczy są bardziej złożone, ale nie miałem ochoty pisać dyrektyw sekcji, aby zbudować tabele odwijania DWARF3 ;-)
Louis Gerbarg
I nie mogę cię winić. :-) Zauważ też, że OS X używa formatu Mach-O zamiast DWARF.
Quinn Taylor,
5
Nikt nie używa DWARF jako formatu binarnego. OS X używa DWARF do symboli debugowania i wykorzystuje tabele
rozwijania
7
Dla porównania napisałem backendy kompilatora dla Mac OS X ;-)
Louis Gerbarg
40

W Objective-C @synchronizedblok obsługuje automatycznie blokowanie i odblokowywanie (a także możliwe wyjątki). Środowisko wykonawcze dynamicznie generuje NSRecursiveLock, który jest powiązany z obiektem, na którym synchronizujesz. Ta dokumentacja Apple wyjaśnia to bardziej szczegółowo. Dlatego nie widzisz komunikatów dziennika z podklasy NSLock - obiekt, na którym synchronizujesz może być czymkolwiek, nie tylko NSLock.

Zasadniczo @synchronized (...)jest to wygodna konstrukcja, która usprawnia kod. Jak większość uproszczonych abstrakcji, wiąże się to z narzutem (pomyśl o tym jako o ukrytym koszcie) i dobrze jest o tym wiedzieć, ale surowa wydajność prawdopodobnie nie jest najważniejszym celem przy stosowaniu takich konstrukcji.

Quinn Taylor
źródło
1
Ten link wygasł. Oto zaktualizowany link: developer.apple.com/library/archive/documentation/Cocoa/…
Ariel Steiner
31

Tak właściwie

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

przekształca się bezpośrednio w:

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

Ten interfejs API jest dostępny od iOS 2.0 i importowany przy użyciu ...

#import <objc/objc-sync.h>
Dirk Theisen
źródło
Czyli nie zapewnia obsługi czysto obsługiwanych wyjątków?
Dustin
Czy to gdzieś jest udokumentowane?
jbat100
6
Jest tam niezrównoważony stabilizator.
Potatoswatter
@Dustin faktycznie to robi, z dokumentów: „Jako środek ostrożności, @synchronizedblok domyślnie dodaje procedurę obsługi wyjątków do chronionego kodu. Ta funkcja obsługi automatycznie zwalnia muteks w przypadku zgłoszenia wyjątku”.
Pieter
objc_sync_enter prawdopodobnie użyje pthread mutex, więc transformacja Louisa jest głębsza i poprawna.
jack
3

Implementacja @synchronized przez Apple jest oprogramowaniem typu open source i można go znaleźć tutaj . Mike Ash napisał dwa naprawdę interesujące posty na ten temat:

W skrócie ma tabelę, która mapuje wskaźniki obiektów (używając ich adresów pamięci jako kluczy) na pthread_mutex_tblokady, które są blokowane i odblokowywane w razie potrzeby.

JP Illanes
źródło