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


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.


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.


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>

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.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.