¿Cómo funciona @synchronized lock / unlock en Objective-C?

201

¿@Synchronized no utiliza "bloquear" y "desbloquear" para lograr la exclusión mutua? ¿Cómo se bloquea / desbloquea entonces?

La salida del siguiente programa es 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];
}
David Lin
fuente
Nota: Relacionado con stackoverflow.com/questions/1215765
Quinn Taylor
10
No necesita anular init si no lo necesita. El tiempo de ejecución llama automáticamente a la implementación de la superclase si no anula un método.
Constantino Tsarouhas
3
Una cosa importante a tener en cuenta es que el código anterior no está sincronizado. El lockobjeto se crea en cada llamada, por lo que nunca habrá un caso en el que un @synchronizedbloque bloquee a otro. Y esto significa que no hay exclusión mutua.) Por supuesto, el ejemplo anterior está haciendo la operación main, por lo que no hay nada que excluir de todos modos, pero uno no debe copiar ciegamente ese código en otro lugar.
Hot Licks
3
Después de leer esta página SO, decidí investigar @synchronized un poco más a fondo y escribir una publicación de blog sobre ella. Puede resultarle útil: rykap.com/objective-c/2015/05/09/synchronized
rjkaplan

Respuestas:

323

La sincronización de nivel de lenguaje Objective-C usa el mutex, al igual que NSLock hace. Semánticamente hay algunas pequeñas diferencias técnicas, pero es básicamente correcto pensar en ellas como dos interfaces separadas implementadas sobre una entidad común (más primitiva).

En particular con un NSLocktiene un bloqueo explícito mientras que con@synchronized que con un bloqueo implícito asociado con el objeto que está utilizando para sincronizar. El beneficio del bloqueo de nivel de lenguaje es que el compilador lo comprende para que pueda lidiar con problemas de alcance, pero mecánicamente se comportan básicamente igual.

Puedes pensar @synchronizedcomo una reescritura del compilador:

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

se transforma 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;
}

Eso no es exactamente correcto porque la transformación real es más compleja y usa bloqueos recursivos, pero debería transmitir el punto.

Louis Gerbarg
fuente
17
También está olvidando el manejo de excepciones que @synchronized hace por usted. Y según tengo entendido, gran parte de esto se maneja en tiempo de ejecución. Esto permite la optimización en cerraduras no vigiladas, etc.
Quinn Taylor
77
Como dije, el material generado real es más complejo, pero no tenía ganas de escribir directivas de sección para construir las tablas de desenrollado DWARF3 ;-)
Louis Gerbarg
Y no te puedo culpar. :-) También tenga en cuenta que OS X utiliza el formato Mach-O en lugar de DWARF.
Quinn Taylor
55
Nadie usa DWARF como formato binario. OS X usa DWARF para símbolos de depuración, y usa tablas de desenrollado DWARF para excepciones de costo cero
Louis Gerbarg
77
Como referencia, he escrito backends del compilador para Mac OS X ;-)
Louis Gerbarg
40

En Objective-C, un @synchronizedbloque maneja el bloqueo y desbloqueo (así como las posibles excepciones) automáticamente para usted. El tiempo de ejecución genera dinámicamente un NSRecursiveLock asociado con el objeto con el que está sincronizando. Esta documentación de Apple lo explica con más detalle. Es por eso que no está viendo los mensajes de registro de su subclase NSLock: el objeto en el que sincroniza puede ser cualquier cosa, no solo un NSLock.

Básicamente, @synchronized (...)es una construcción conveniente que simplifica su código. Al igual que la mayoría de las abstracciones simplificadoras, tiene gastos generales asociados (piense en ello como un costo oculto), y es bueno ser consciente de eso, pero el rendimiento bruto probablemente no sea el objetivo supremo cuando se usan tales construcciones de todos modos.

Quinn Taylor
fuente
1
Ese enlace ha expirado. Aquí está el enlace actualizado: developer.apple.com/library/archive/documentation/Cocoa/…
Ariel Steiner
31

Realmente

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

se transforma directamente en:

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

Esta API disponible desde iOS 2.0 e importada usando ...

#import <objc/objc-sync.h>
Dirk Theisen
fuente
¿Entonces no brinda soporte para manejar limpiamente las excepciones lanzadas?
Dustin
¿Está esto documentado en alguna parte?
jbat100
66
Hay un aparato ortopédico desequilibrado allí.
Potatoswatter
@Dustin en realidad lo hace, desde los documentos: "Como medida de precaución, el @synchronizedbloque agrega implícitamente un controlador de excepción al código protegido. Este controlador libera automáticamente el mutex en caso de que se produzca una excepción".
Pieter
objc_sync_enter probablemente usará pthread mutex, por lo que la transformación de Louis es más profunda y correcta.
Jack
3

La implementación de Apple de @synchronized es de código abierto y se puede encontrar aquí . Mike ash escribió dos publicaciones realmente interesantes sobre este tema:

En pocas palabras, tiene una tabla que asigna punteros de objetos (usando sus direcciones de memoria como claves) a pthread_mutex_tbloqueos, que se bloquean y desbloquean según sea necesario.

JP Illanes
fuente
-4

Simplemente asocia un semáforo con cada objeto, y lo usa.

Pavel Minaev
fuente
Técnicamente, crea un bloqueo de mutex, pero la idea básica es correcta. Vea la diva de Apple en: developer.apple.com/documentation/Cocoa/Conceptual/…
Mark Bessey
3
No solo un mutex, sino un bloqueo recursivo.
kperryua