Comment le verrouillage / déverrouillage @synchronized dans Objective-C?

201

@Synchronized n'utilise-t-il pas "verrouiller" et "déverrouiller" pour réaliser l'exclusion mutuelle? Comment ça se verrouille / déverrouille alors?

La sortie du programme suivant est uniquement "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
la source
Remarque: liée à stackoverflow.com/questions/1215765
Quinn Taylor
10
Vous n'avez pas besoin de remplacer init si vous n'en avez pas besoin. Le runtime appelle automatiquement l'implémentation de la superclasse si vous ne remplacez pas une méthode.
Constantino Tsarouhas
3
Une chose importante à noter est que le code ci-dessus n'est pas synchronisé. L' lockobjet est créé à chaque appel, il n'y aura donc jamais de cas où un @synchronizedbloc en verrouille un autre. Et cela signifie qu'il n'y a pas d'exclusion mutuelle.) Bien sûr, l'exemple ci-dessus fait l'opération en main, donc il n'y a rien à exclure de toute façon, mais il ne faut pas copier aveuglément ce code ailleurs.
Hot Licks
3
Après avoir lu cette page SO, j'ai décidé d'enquêter sur @synchronized un peu plus en profondeur et d'y écrire un article de blog. Vous pouvez le trouver utile: rykap.com/objective-c/2015/05/09/synchronized
rjkaplan

Réponses:

323

La synchronisation au niveau du langage Objective-C utilise le mutex, tout comme le NSLockfait. Sémantiquement, il existe de petites différences techniques, mais il est fondamentalement correct de les considérer comme deux interfaces distinctes implémentées au-dessus d'une entité commune (plus primitive).

En particulier avec un, NSLockvous avez un verrou explicite tandis qu'avec @synchronizedvous avez un verrou implicite associé à l'objet que vous utilisez pour synchroniser. L'avantage du verrouillage au niveau du langage est que le compilateur le comprend afin qu'il puisse traiter les problèmes de portée, mais mécaniquement, ils se comportent essentiellement de la même manière.

Vous pouvez penser @synchronizedà une réécriture du compilateur:

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

se transforme en:

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

Ce n'est pas exactement correct car la transformation réelle est plus complexe et utilise des verrous récursifs, mais elle devrait faire passer le message.

Louis Gerbarg
la source
17
Vous oubliez également la gestion des exceptions que @synchronized fait pour vous. Et si je comprends bien, une grande partie de cela est gérée lors de l'exécution. Cela permet d'optimiser les verrous non intentionnels, etc.
Quinn Taylor
7
Comme je l'ai dit, le contenu généré est plus complexe, mais je n'ai pas eu envie d'écrire des directives de section pour construire les tables de déroulement DWARF3 ;-)
Louis Gerbarg
Et je ne peux pas te blâmer. :-) Notez également que OS X utilise le format Mach-O au lieu de DWARF.
Quinn Taylor
5
Personne n'utilise DWARF comme format binaire. OS X utilise DWARF pour les symboles de débogage et il utilise des tables de
déroulement
7
Pour référence, j'ai écrit des backends de compilateur pour Mac OS X ;-)
Louis Gerbarg
40

Dans Objective-C, un @synchronizedbloc gère automatiquement le verrouillage et le déverrouillage (ainsi que les exceptions possibles). Le runtime génère dynamiquement essentiellement un NSRecursiveLock qui est associé à l'objet sur lequel vous effectuez la synchronisation. Cette documentation Apple l' explique plus en détail. C'est pourquoi vous ne voyez pas les messages de journal de votre sous-classe NSLock - l'objet sur lequel vous synchronisez peut être n'importe quoi, pas seulement un NSLock.

Fondamentalement, @synchronized (...)c'est une construction pratique qui rationalise votre code. Comme la plupart des abstractions simplificatrices, il a associé des frais généraux (pensez-y comme un coût caché), et il est bon d'en être conscient, mais les performances brutes ne sont probablement pas l'objectif suprême lors de l'utilisation de telles constructions de toute façon.

Quinn Taylor
la source
1
Ce lien a expiré. Voici le lien mis à jour: developer.apple.com/library/archive/documentation/Cocoa/…
Ariel Steiner
31

Réellement

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

se transforme directement en:

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

Cette API disponible depuis iOS 2.0 et importée en utilisant ...

#import <objc/objc-sync.h>
Dirk Theisen
la source
Donc, il ne fournit aucun support pour gérer proprement les exceptions levées?
Dustin
Est-ce documenté quelque part?
jbat100
6
Il y a là une attelle déséquilibrée.
Potatoswatter
@Dustin en fait, d'après les documents: "Par mesure de précaution, le @synchronizedbloc ajoute implicitement un gestionnaire d'exceptions au code protégé. Ce gestionnaire libère automatiquement le mutex en cas de levée d'une exception."
Pieter
objc_sync_enter utilisera probablement pthread mutex, donc la transformation de Louis est plus profonde et correcte.
jack
3

L'implémentation d'Apple de @synchronized est open source et vous pouvez la trouver ici . Mike Ash a écrit deux articles très intéressants sur ce sujet:

En bref, il a une table qui mappe les pointeurs d'objets (en utilisant leurs adresses mémoire comme clés) sur les pthread_mutex_tverrous, qui sont verrouillés et déverrouillés selon les besoins.

JP Illanes
la source
-4

Il associe simplement un sémaphore à chaque objet et l'utilise.

Pavel Minaev
la source
Techniquement, cela crée un verrou mutex, mais l'idée de base est correcte. Voir la diva Apple sur: developer.apple.com/documentation/Cocoa/Conceptual/…
Mark Bessey
3
Pas seulement un mutex, mais un verrou récursif.
kperryua