Come funziona il blocco / sblocco sincronizzato di @ in Objective-C?


201

@Synchronized non utilizza "lock" e "unlock" per ottenere l'esclusione reciproca? Come si blocca / sblocca quindi?

L'output del seguente programma è solo "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
Non è necessario sovrascrivere init se non è necessario. Il runtime chiama automaticamente l'implementazione della superclasse se non si ignora un metodo.
Constantino Tsarouhas,

3
Una cosa importante da notare è che il codice sopra non è sincronizzato. L' lockoggetto viene creato ad ogni chiamata, quindi non ci sarà mai un caso in cui un @synchronizedblocco ne blocca un altro. E questo significa che non c'è esclusione reciproca.) Naturalmente, l'esempio sopra sta eseguendo l'operazione main, quindi non c'è nulla da escludere comunque, ma non si dovrebbe copiare ciecamente quel codice altrove.
Hot Licks,

3
Dopo aver letto questa pagina SO, ho deciso di indagare un po 'più a fondo su @synchronized e di scrivere un post sul blog. Potrebbe esserti
rjkaplan

Risposte:


323

La sincronizzazione a livello di linguaggio Objective-C utilizza il mutex, proprio come NSLockfa. Semanticamente ci sono alcune piccole differenze tecniche, ma è fondamentalmente corretto pensarle come due interfacce separate implementate su un'entità comune (più primitiva).

In particolare con a NSLockhai un blocco esplicito mentre con @synchronizedte hai un blocco implicito associato all'oggetto che stai usando per sincronizzare. Il vantaggio del blocco a livello di lingua è che il compilatore lo comprende in modo da poter gestire i problemi di scoping, ma meccanicamente si comportano sostanzialmente allo stesso modo.

Puoi pensare @synchronizeda una riscrittura del compilatore:

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

si trasforma 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;
}

Ciò non è esattamente corretto perché la trasformazione effettiva è più complessa e utilizza blocchi ricorsivi, ma dovrebbe ottenere il punto.


17
Stai anche dimenticando la gestione delle eccezioni che @synchronized fa per te. E a quanto ho capito, gran parte di questo è gestito in fase di esecuzione. Ciò consente l'ottimizzazione su blocchi non contenti, ecc.
Quinn Taylor

7
Come ho detto, le cose generate in realtà sono più complesse, ma non mi andava di scrivere direttive di sezione per costruire le tabelle di svolgimento DWARF3 ;-)
Louis Gerbarg

E non posso biasimarti. :-) Nota anche che OS X utilizza il formato Mach-O invece di DWARF.
Quinn Taylor,

5
Nessuno utilizza DWARF come formato binario. OS X utilizza DWARF per i simboli di debug e utilizza le tabelle di svolgimento DWARF per eccezioni a costo zero
Louis Gerbarg

7
Per riferimento, ho scritto backend del compilatore per Mac OS X ;-)
Louis Gerbarg

40

In Objective-C, un @synchronizedblocco gestisce automaticamente il blocco e lo sblocco (nonché eventuali eccezioni). Il runtime genera essenzialmente un NSRecursiveLock associato all'oggetto su cui si sta sincronizzando. Questa documentazione di Apple lo spiega in modo più dettagliato. Questo è il motivo per cui non vedi i messaggi di registro della tua sottoclasse NSLock: l'oggetto su cui sincronizzi può essere qualsiasi cosa, non solo un NSLock.

Fondamentalmente, @synchronized (...)è un costrutto pratico che semplifica il tuo codice. Come la maggior parte delle astrazioni semplificanti, ha un sovraccarico associato (pensalo come un costo nascosto), ed è bene esserne consapevoli, ma le prestazioni grezze non sono probabilmente l'obiettivo supremo quando si usano tali costrutti.


1
Tale link è scaduto. Ecco il link aggiornato: developer.apple.com/library/archive/documentation/Cocoa/…
Ariel Steiner

31

In realtà

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

si trasforma direttamente in:

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

Questa API è disponibile da iOS 2.0 e importata utilizzando ...

#import <objc/objc-sync.h>

Quindi non fornisce alcun supporto per gestire in modo pulito le eccezioni generate?
Dustin,

Questo è documentato da qualche parte?
jbat100,

6
C'è un tutore sbilanciato lì.
Potatoswatter l'

@Dustin in realtà lo fa, dai documenti: "Come misura precauzionale, il @synchronizedblocco aggiunge implicitamente un gestore di eccezioni al codice protetto. Questo gestore rilascia automaticamente il mutex nel caso in cui venga generata un'eccezione."
Pieter,

objc_sync_enter probabilmente utilizzerà pthread mutex, quindi la trasformazione di Louis è più profonda e corretta.
jack

3

L'implementazione di Apple di @synchronized è open source e può essere trovata qui . Mike Ash ha scritto due post davvero interessanti su questo argomento:

In breve ha una tabella che mappa i puntatori agli oggetti (usando i loro indirizzi di memoria come chiavi) ai pthread_mutex_tblocchi, che sono bloccati e sbloccati secondo necessità.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.