Может ли статическая переменная использоваться в качестве параметра @synchronized? - PullRequest
0 голосов
/ 31 августа 2018

Мы хотим гарантировать безопасность потоков для статической переменной. Мы использовали другую статическую переменную в качестве объекта в директиве @synchronized. Как это:

static NSString *_saveInProgressLock = @"SaveInProgressLock";
static BOOL _saveInProgress;

+ (BOOL)saveInProgress {
    @synchronized(_saveInProgressLock) {
        return _saveInProgress;
    }
}

+ (void)setSaveInProgress:(BOOL)save {
    @synchronized(_saveInProgressLock) {
        _saveInProgress = save;
    }
}

У нас возникли проблемы с приложением, которое в данный момент находится в магазине и может быть воспроизведено, если для переменной _saveInProgress установлено значение NO. Видите ли вы какие-либо проблемы с вышеуказанным кодом?

Чем он отличается от этого?

static BOOL _saveInProgress;

+ (BOOL)saveInProgress {
    @synchronized([MyClass class]) {
        return _saveInProgress;
    }
}

+ (void)setSaveInProgress:(BOOL)save {
    @synchronized([MyClass class]) {
        _saveInProgress = save;
    }
}

1 Ответ

0 голосов
/ 31 августа 2018

tl; dr: Это совершенно безопасно, если строковый литерал уникален. Если он не уникален, могут быть (мягкие) проблемы, но обычно только в режиме выпуска. Однако может быть проще реализовать это.


Блоки

@synchronized реализованы с использованием функций времени выполнения objc_sync_enter и objc_sync_exit ( source ). Эти функции реализованы с использованием глобальной (но objc-internal) боковой таблицы блокировок, которая определяется значениями указателя. На уровне C-API вы также можете заблокировать (void *)42 или фактически любое значение указателя. Не имеет значения, жив ли объект, потому что указатель никогда не разыменовывается. Однако компилятор objc отказывается компилировать выражение @synchronized(obj), если obj не выполняет статическую проверку типов для типа id (из которых NSString * является подтипом, так что все в порядке) и, возможно, он сохраняет объект (I я не уверен в этом), поэтому вы должны использовать его только с объектами.

Есть две критические точки, которые следует учитывать:

  • , если obj, по которому вы синхронизируете, является NULL-указателем (nil в Objective C), тогда objc_sync_enter и objc_sync_exit - это no-ops, и это приводит к нежелательной ситуации, когда блок выполняется абсолютно без блокировки.
  • Если вы используете одно и то же строковое значение для разных блоков @synchronized, компилятор может быть достаточно умен, чтобы сопоставить их с одним и тем же адресом указателя. Может быть, компилятор не делает этого сейчас, но это вполне допустимая оптимизация, которую Apple может внедрить в будущем. Поэтому вы должны убедиться, что вы используете уникальные имена. В этом случае два разных блока @synchronized могут случайно использовать одну и ту же блокировку, когда программист хотел использовать разные блокировки. Кстати, вы также можете использовать [NSObject new] в качестве объекта блокировки.

Синхронизация на объекте класса ([MyClass class]) совершенно безопасна и тоже подойдет.


Теперь для более простого пути. Если у вас есть только одна переменная BOOL, которую вы хотите использовать в качестве атомарной, вы можете использовать программирование без блокировки:

static BOOL _saveInProgress;

+ (BOOL)saveInProgress {
    __sync_synchronize();
    return _saveInProgress;
}

+ (void)setSaveInProgress:(BOOL)save {
    _saveInProgress = save;
    __sync_synchronize();
}

Это имеет гораздо лучшую производительность и столь же безопасен для потоков. __sync_synchronize() - это барьер памяти.


Обратите внимание, что безопасность обоих решений зависит от того, как вы их используете. Если у вас есть метод сохранения где-то, который выглядит следующим образом:

+ (void)save { // line 21
    if(![self saveInProgress]) { // line 22
        [self setSaveInProgress:YES]; // line 23
        // ... do stuff ...
        [self setSaveInProgress:NO]; // line 40
    }
}

, что +save метод вообще не является потокобезопасным, потому что между строкой 22 и 23 есть условие гонки (не хочу вдаваться в подробности здесь .. просто задайте новый вопрос, если вам нужна дополнительная информация.)

...