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 есть условие гонки (не хочу вдаваться в подробности здесь .. просто задайте новый вопрос, если вам нужна дополнительная информация.)