Потокобезопасность локальной статической переменной на Objective-C

Поскольку следующий код является довольно распространенным шаблоном в Objective-C для создания экземпляра и обеспечения его потокобезопасности. Однако эта потокобезопасность основана на одном важном условии, что локальная статическая переменная является потокобезопасной, что гарантируется компилятором, что означает, что статический указатель _sharedCache будет гарантированно создан в потокобезопасном режиме, однако я не могу узнайте любую документацию, упоминающую об этом. Кто-нибудь может предоставить мне более уверенное доказательство? (Поскольку люди здесь сосредотачиваются на изменяемом наборе, который я использовал в начале, поэтому я просто меняю его на NSCache, это действительно не главное. Я говорю о безопасности потоков при создании локального статического указателя здесь (не экземпляр, на который указывает этот указатель))

+ (NSCache*)sharedCache {
  static NSCache* _sharedCache = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _sharedCache = [[NSCache alloc] init];
  });
  return _sharedCache;
}

На всякий случай, если я не ясно описал условие гонки, считайте, что два потока одновременно вызывают этот API и одновременно проверяют, существует ли этот статический указатель _sharedCache, если нет, они сами создадут новый статический указатель. Тогда это могут быть два статических указателя NSMutableSet здесь, даже dispatch_once гарантировал, что инициализация экземпляра произошла только один раз, то есть только для одного из этих двух статических указателей, а затем после блока dispatch_once первый вызывающий объект получает удовлетворительный результат, но второй вызывающий абонент просто возвращает нулевой указатель;

Рассмотрим этот случай, A и B - два потока, ввели этот код одновременно, A проверил статический указатель "_sharedSet" здесь и обнаружил, что здесь нет такого указателя (указатель является экземпляром unsigned long), поэтому он создает его и присваивает 0 к нему, теперь этот указатель хранится в адресе памяти (0xAAAAAAAA), одновременно B сделал то же самое, создал статический указатель в куче, но с адресом памяти (0xBBBBBBBB), затем dispatch_once заблокировал оба потока, пока они не закончатся, поэтому он назначил новое значение указателя с этим созданным экземпляром NSSet и присвоило это значение адресу 0xAAAAAAAA, однако значение по адресу 0xBBBBBBBB по-прежнему равно 0, потому что его никто не обновляет, поэтому поток B просто возвращает nil. Итак, в основном мой вопрос не о сомнении в dispatch_once, а о безопасности потоков при создании локальной статической переменной, это действительная проблема на C++, пока C11 не разрешил ее. Мне просто интересно, заметил ли Clang эту проблему.

И этот вопрос на самом деле не о dispatch_once, а о локальной статической переменной, быть конкретной переменной, не являющейся объектом, как упомянутый здесь указатель, или просто изменить способ спросить, если следующий код, вызванный в соревновательном потоков, будет ли этот "_intBuffer" гарантировать только один экземпляр в куче?

    +(int)sharedIntBuffer {
        static int _intBuffer = 0;
        return _intBuffer;
    }

person Pei    schedule 08.09.2014    source источник
comment
NSMutableSet не является потокобезопасным. Итак, нет, приведенный выше код не является потокобезопасным, но не по причинам, которые вас беспокоят. Смотрите ответ @Caleb ниже.   -  person bbum    schedule 09.09.2014
comment
Большое спасибо за ваш повтор, однако это не моя точка зрения, это моя вина, что я указал неподходящую демонстрацию в начале. И я только что обновил свой вопрос, чтобы избежать дальнейших недоразумений.   -  person Pei    schedule 09.09.2014


Ответы (3)


Более серьезная потенциальная проблема с вашим кодом заключается в том, что вы совместно используете изменяемый набор. Как объясняет Гнашер, код, который вы показываете для выделения набора с использованием dispatch_once(), в порядке по причинам, объясненным в этом ответе. Но как только это будет сделано, вы будете делиться изменяемым набором потенциально между потоками. Если вы не предпримете шаги для синхронизации доступа к этому набору, у вас могут легко возникнуть проблемы, когда два или более потока изменяют набор одновременно. Самый простой и надежный способ предотвратить подобные проблемы — инкапсулировать набор в класс, который обеспечивает доступ к набору потокобезопасным способом, например используя последовательную очередь отправки.

person Caleb    schedule 08.09.2014
comment
Да, это известная проблема, но не в центре внимания, просто для демонстрации здесь. - person Pei; 09.09.2014

В этом весь смысл dispatch_once. Код dispatch_once со стопроцентной гарантией будет выполнен ровно один раз, поэтому он называется dispatch_once. И на сто процентов гарантируется, что второй и последующие вызовы будут ожидать завершения первого вызова и завершения всех операций записи в память.

person gnasher729    schedule 08.09.2014
comment
Рассмотрим этот случай, A и B - два потока, ввели этот код одновременно, A проверил статический указатель _sharedSet здесь и обнаружил, что здесь нет такого указателя (указатель является экземпляром unsigned long), поэтому он создает один и присваивает ему 0 , теперь этот указатель хранится в адресе памяти (0xAAAAAAAA), одновременно B сделал то же самое, создал статический указатель в куче, но с адресом памяти (0xBBBBBBBB), затем dispatch_once заблокировал оба потока до завершения, поэтому он назначил новый указатель значение с этим созданным экземпляром NSSet и присвоил это значение адресу 0xAAAAAAAA, - person Pei; 09.09.2014
comment
однако значение по адресу 0xBBBBBBBB по-прежнему равно 0, потому что его никто не обновляет, поэтому поток B просто возвращает nil. Итак, в основном мой вопрос не о сомнении в dispatch_once, а о безопасности потоков при создании локальной статической переменной, это действительная проблема на C++, пока C11 не разрешил ее. Мне просто интересно, заметил ли Clang эту проблему, - person Pei; 09.09.2014

После разговора с парнем, который хорошо знает компилятор, я просто понял, что так же, как и глобальная статическая переменная, локальная статическая переменная также инициализируется до запуска main(). Только его нельзя посетить, потому что нет ссылки, чтобы указать его. Итак, вернемся к делу, что по вопросу. Даже два потока входят в эти +(NSCahce*)sharedCache одновременно, будет только один экземпляр статического указателя _sharedCache. Это означает, что условие гонки здесь недействительно.

person Pei    schedule 04.11.2014