Как уже упоминались в linked answer to a similar question, справочная документация говорит:
предикат должен указывать на переменный, хранящихся в глобальном или статические scope. Результат использования предиката с автоматическим или динамическим хранилищем не определен.
Общая озабоченность хорошо указана в этом ответе. Тем не менее, можно заставить его работать. Чтобы уточнить: проблема заключается в том, что хранилище для предиката должно быть надежно обнулено при инициализации. С помощью статической/глобальной семантики это гарантируется. Теперь я знаю, о чем вы думаете, «... но объекты Objective-C также обнуляются на init!», И вы были бы в целом правы. Там, где проблема возникает, это переупорядочение чтения/записи. Некоторые архитектуры (например, ARM) имеют модели памяти weakly consistent, что означает, что считывание/запись в память может быть переупорядочено до тех пор, пока сохраняется первоначальное намерение основного потока последовательности выполнения. В этом случае переупорядочение может потенциально оставить вас в ситуации, когда операция «обнуления» задерживается, так что после после другой поток пытается прочитать токен. (т.е. -init возвращает, указатель объекта становится видимым для другого потока, который другой поток пытается получить доступ к токену, но он все еще мусор, потому что операция обнуления еще не была выполнена.) Чтобы избежать этой проблемы, вы можете добавить вызов OSMemoryBarrier()
до конца вашего метода -init
, и все должно быть в порядке. (Обратите внимание, что существует ненулевое ограничение производительности для добавления барьера памяти здесь и к барьерам памяти в целом.) details of memory barriers оставлены как «дальнейшее чтение» (но если вы собираетесь полагаться на них, рекомендуется понимать их, по крайней мере, концептуально.)
Моя догадка заключается в том, что «запрет» на использование dispatch_once
с неглобальным/статическим хранилищем связан с тем, что нестандартные исполнения и барьеры памяти сложны темы, преодоление препятствий, трудно, их неправильное поведение приводит к чрезвычайно тонким и труднодоступным ошибкам и, возможно, самое важное (хотя я не измерял его эмпирически), вводя необходимый барьер памяти для обеспечения безопасности использование dispatch_once_t
в ivar почти наверняка отрицает некоторые (все?) преимущества в производительности, которые dispatch_once
имеет ov er "классические" блокировки.
Также обратите внимание, что существует два типа «переупорядочения».«Там происходит переупорядочение, которое происходит как оптимизация компилятора (это переупорядочение, которое выполняется с помощью ключевого слова volatile
), а затем происходит переупорядочение на аппаратном уровне по-разному на разных архитектурах. Этот перезарядка аппаратного уровня это переупорядочение, которое управляется/управляется барьером памяти (например, ключевое слово volatile
не является достаточным.)
ОП спрашивал конкретно о способе «закончить один раз». Один пример (что для моих глаз появляется безопасный/правильный) для такого шаблона можно увидеть в классе ReactiveCocoa RACDisposable, который хранит нуль или один блок для запуска во время утилизации и гарантирует, что «одноразовый» используется только один раз и что блок, если он есть, только когда-либо называется один раз. Это выглядит так:
@interface RACDisposable()
{
void * volatile _disposeBlock;
}
@end
...
@implementation RACDisposable
// <snip>
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposeBlock = (__bridge void *)self;
OSMemoryBarrier();
return self;
}
// <snip>
- (void)dispose {
void (^disposeBlock)(void) = NULL;
while (YES) {
void *blockPtr = _disposeBlock;
if (OSAtomicCompareAndSwapPtrBarrier(blockPtr, NULL, &_disposeBlock)) {
if (blockPtr != (__bridge void *)self) {
disposeBlock = CFBridgingRelease(blockPtr);
}
break;
}
}
if (disposeBlock != nil) disposeBlock();
}
// <snip>
@end
Он использует в INIT OSMemoryBarrier()
, так же, как вы бы использовать для dispatch_once
, то он использует OSAtomicCompareAndSwapPtrBarrier
, который, как следует из названия, предполагает барьер памяти, атомарный «щелкнуть выключатель». В случае, если неясно, что здесь происходит, то, что на -init
время ivar установлено на self
. Это условие используются в качестве «маркеров», чтобы различать между случаями «нет блока, но мы не склонны» и «был блоком, но мы уже располагали.»
В практическом плане , если препятствия памяти кажутся непрозрачными и загадочными для вас, мой совет будет состоять в том, чтобы просто использовать классические блокировки, пока вы не заметите, что эти классические блоки блокировки вызывают реальные и измеримые проблемы производительности для вашего приложения.
См [Могу ли я объявить dispatch_once_t предикат в качестве переменной-члена вместо статические] (http://stackoverflow.com/questions/13856037/can-i-declare-dispatch-once-t-predicate- как-член-переменной вместо-о-статического). – CRD
Итак, вопрос в том, что это лучший способ сделать это –