Como o bloqueio / desbloqueio sincronizado no Objective-C?

201

@Synchronized não usa "lock" e "unlock" para obter exclusão mútua? Como ele bloqueia / desbloqueia então?

A saída do programa a seguir é apenas "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
fonte
Nota: Relacionado com stackoverflow.com/questions/1215765
Quinn Taylor
10
Você não precisa substituir o init se não precisar. O tempo de execução chama automaticamente a implementação da superclasse se você não substituir um método.
Constantino Tsarouhas
3
Uma coisa importante a ser observada é que o código acima não está sincronizado. O lockobjeto é criado em todas as chamadas, portanto, nunca haverá um caso em que um @synchronizedbloco bloqueie outro. E isso significa que não há exclusão mútua.) É claro que o exemplo acima está executando a operação main, portanto não há nada a excluir, mas não se deve copiar cegamente esse código em outro lugar.
Hot Licks
3
Depois de ler esta página da SO, decidi investigar o @synchronized um pouco mais detalhadamente e escrever uma postagem no blog. Você pode achar útil: rykap.com/objective-c/2015/05/09/synchronized
rjkaplan 18/16

Respostas:

323

A sincronização no nível de linguagem Objective-C usa o mutex, assim como NSLockfaz. Semanticamente, existem algumas pequenas diferenças técnicas, mas é basicamente correto pensar nelas como duas interfaces separadas implementadas sobre uma entidade comum (mais primitiva).

Em particular, NSLockvocê tem um bloqueio explícito, enquanto @synchronizedque um bloqueio implícito está associado ao objeto que você está usando para sincronizar. O benefício do bloqueio no nível do idioma é que o compilador o entende para lidar com problemas de escopo, mas mecanicamente eles se comportam basicamente da mesma forma.

Você pode pensar @synchronizedem uma reescrita do compilador:

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

é transformado em:

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

Isso não é exatamente correto, porque a transformação real é mais complexa e usa bloqueios recursivos, mas deve passar o ponto.

Louis Gerbarg
fonte
17
Você também está esquecendo a manipulação de exceções que o @synchronized faz por você. E pelo que entendi, muito disso é tratado em tempo de execução. Isso permite a otimização on fechaduras uncontended, etc.
Quinn Taylor
7
Como eu disse, o material real gerada é mais complexa, mas eu não tinha vontade de escrever directivas seção em construção ordem as tabelas desenrolamento DWARF3 ;-)
Louis Gerbarg
E eu não posso te culpar. :-) Observe também que o OS X usa o formato Mach-O em vez do DWARF.
9115 Quinn Taylor
5
Ninguém usa o DWARF como um formato binário. OS X faz uso do anão por símbolos de depuração, e utiliza tabelas desenrolamento do anão por zero de exceções custo
Louis Gerbarg
7
Para referência, eu escrevi backends do compilador para Mac OS X ;-)
Louis Gerbarg
40

No Objective-C, um @synchronizedbloco lida com o bloqueio e desbloqueio (bem como com possíveis exceções) automaticamente para você. O tempo de execução dinamicamente gera essencialmente um NSRecursiveLock associado ao objeto no qual você está sincronizando. Esta documentação da Apple explica mais detalhadamente. É por isso que você não está vendo as mensagens de log da sua subclasse NSLock - o objeto no qual você sincroniza pode ser qualquer coisa, não apenas um NSLock.

Basicamente, @synchronized (...)é uma construção de conveniência que otimiza seu código. Como a maioria das abstrações simplificadoras, ela tem sobrecarga associada (pense nisso como um custo oculto), e é bom estar ciente disso, mas o desempenho bruto provavelmente não é o objetivo supremo ao usar essas construções de qualquer maneira.

Quinn Taylor
fonte
1
Esse link expirou. Aqui está o link atualizado: developer.apple.com/library/archive/documentation/Cocoa/…
Ariel Steiner
31

Na realidade

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

transforma diretamente em:

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

Esta API está disponível desde o iOS 2.0 e importada usando ...

#import <objc/objc-sync.h>
Dirk Theisen
fonte
Portanto, ele não fornece suporte para o tratamento limpo de exceções lançadas?
Dustin
Isso está documentado em algum lugar?
precisa saber é o seguinte
6
Há uma chave desequilibrada lá.
Potatoswatter
@Dustin, na verdade, nos documentos: "Como medida de precaução, o @synchronizedbloco adiciona implicitamente um manipulador de exceções ao código protegido. Esse manipulador libera automaticamente o mutex no caso de uma exceção ser lançada".
Pieter
O objc_sync_enter provavelmente usará o pthread mutex, portanto a transformação de Louis é mais profunda e correta.
jack
3

A implementação do @synchronized pela Apple é de código aberto e pode ser encontrada aqui . Mike ash escreveu dois posts realmente interessantes sobre esse assunto:

Em poucas palavras, há uma tabela que mapeia ponteiros de objetos (usando seus endereços de memória como chaves) para pthread_mutex_tbloqueios, que são bloqueados e desbloqueados conforme necessário.

JP Illanes
fonte
-4

Apenas associa um semáforo a cada objeto e o usa.

Pavel Minaev
fonte
Tecnicamente, ele cria um bloqueio mutex, mas a idéia básica está correta. Veja a diva da Apple em: developer.apple.com/documentation/Cocoa/Conceptual/…
Mark Bessey
3
Não apenas um mutex, mas um bloqueio recursivo.
kperryua