Як @synchronized блокування / розблокування в Objective-C?


201

Чи @synchronized не використовує "lock" та "unlock" для досягнення взаємного виключення? Як це зробити блокування / розблокування тоді?

Вихід з наступної програми - лише "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];
}

Примітка: Пов’язано зі stackoverflow.com/questions/1215765
Квінн Тейлор,

10
Вам не потрібно переосмислювати init, якщо він вам не потрібен. Виконання автоматично викликає реалізацію суперкласу, якщо ви не перекриєте метод.
Константіно Царухас

3
Важливо зазначити, що наведений вище код не синхронізований. lockОб'єкт створюється при кожному виклику, так що ніколи не буде випадок , коли один @synchronizedблок блокує інший. А це означає, що взаємного виключення немає.) Звичайно, наведений вище приклад робить операцію main, тому все одно нічого не виключати, але не слід сліпо копіювати цей код в іншому місці.
Гарячі лизання

3
Прочитавши цю сторінку SO, я вирішив трохи більш ретельно дослідити @synchronized і написати на ньому допис у блозі. Вам може бути корисним: rykap.com/objective-c/2015/05/09/synchronized
rjkaplan

Відповіді:


323

Синхронізація рівня мови Objective-C використовує mutex, як NSLockі у випадку. Семантично є деякі невеликі технічні відмінності, але в основному правильно вважати їх двома окремими інтерфейсами, реалізованими поверх загальної (більш примітивної) сутності.

Зокрема, у NSLockвас є явний замок, тоді як у @synchronizedвас є неявний замок, пов'язаний з об'єктом, який ви використовуєте для синхронізації. Перевага блокування мовного рівня полягає в тому, що компілятор розуміє це, щоб він міг вирішувати питання визначення обсягу, але механічно вони поводяться в основному однаково.

Ви можете вважати @synchronizedперезапис компілятора:

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

перетворюється на:

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

Це не зовсім правильно, оскільки власне перетворення є складнішим і використовує рекурсивні блокування, але воно повинно отримати точку впоперек.


17
Ви також забуваєте обробляти винятки, які робить для вас @synchronized. І як я розумію, значна частина цього обробляється під час виконання. Це дозволяє оптимізувати незаперечні замки тощо.
Квінн Тейлор

7
Як я вже говорив, фактично створені речі є складнішими, але мені не здавалося, що я буду писати директивні розділи для того, щоб створити розмотані таблиці DWARF3 ;-)
Луї Гербарг

І я не можу тебе звинуватити. :-) Також зауважте, що OS X використовує формат Mach-O замість DWARF.
Квінн Тейлор

5
Ніхто не використовує DWARF як бінарний формат. OS X використовує DWARF для налагодження символів, і він використовує таблиці розкручування DWARF для виключень з нульовою вартістю
Луї Гербарг

7
Для довідки я написав компіляторні програми для Mac OS X ;-)
Луї Гербарг

40

У Objective-C @synchronizedблок обробляє блокування та розблокування (а також можливі винятки) автоматично. Час виконання динамічно по суті створює NSRecursiveLock, який асоціюється з об'єктом, над яким ви синхронізуєте. Ця документація Apple пояснює це більш докладно. Ось чому ви не бачите повідомлень журналу з підкласу NSLock - об'єктом, на який ви синхронізуєтесь, може бути що завгодно, не тільки NSLock.

В основному, @synchronized (...)це зручна конструкція, яка впорядковує ваш код. Як і більшість спрощених абстракцій, вона пов'язує накладні витрати (думайте про це як про приховану вартість), і це добре знати, але сирі показники, мабуть, не є найвищою метою при використанні таких конструкцій у будь-якому випадку.


1
Термін дії цього посилання закінчився. Ось оновлене посилання: developer.apple.com/library/archive/documentation/Cocoa/…
Аріель Штайнер

31

Насправді

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

перетворюється безпосередньо в:

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

Цей API доступний з iOS 2.0 та імпортується за допомогою ...

#import <objc/objc-sync.h>

Отже, він не підтримує чіткого поводження з викинутими винятками?
Дастін

Це десь задокументовано?
jbat100

6
Там є неврівноважена дужка.
Potatoswatter

@Dustin насправді це робить, з docs: "Як запобіжний захід, @synchronizedблок неявно додає обробник винятків до захищеного коду. Цей обробник автоматично випускає мютекс у випадку, якщо буде викинуто виняток."
Пітер

objc_sync_enter, ймовірно, буде використовувати мутекс pthread, тому перетворення Луї є більш глибоким і правильним.
джек

3

Реалізація @synchronized від Apple є відкритим кодом, і її можна знайти тут . Майк Еш написав дві справді цікаві публікації на цю тему:

У двох словах, вона має таблицю, яка відображає вказівники об'єктів (використовуючи їх адреси пам'яті як ключі) на pthread_mutex_tблоки, які блокуються та розблоковуються за потребою.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.