Ориентированное на многопотоковое исполнение инстанцирование одиночного элемента

Который один метод синхронизации использовать для обеспечения одиночного элемента остается одиночным элементом?

+(Foo*)sharedInstance
{
   @synchronized(self)
   {
      if (nil == _sharedInstance)
      {
         _sharedInstance = [[Foo alloc] init];
         ...
      }
   }
   return _sharedInstance;
}

или использование взаимного исключения?

#import <pthread.h>

static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;

+(Foo*)sharedInstance
{
   pthread_mutex_lock(&_mutex);
   if (nil == _sharedInstance)
   {
      _sharedInstance = [[Foo alloc] init];
      ...
   }
   pthread_mutex_unlock(&_mutex);
   return _sharedInstance;
}

Хм.. какие-либо комментарии к этому?

33
задан bbum 23 January 2016 в 08:26
поделиться

3 ответа

Обязательно прочтите обсуждение этого вопроса / ответа. Почему мы должны разделять вызовы alloc и init, чтобы избежать взаимоблокировок в Objective-C?


Чтобы расширить проблему состояния гонки; настоящее исправление состоит в том, чтобы не допускать неопределенной инициализации в вашем приложении. Неопределенная или отложенная инициализация приводит к поведению, которое может легко измениться из-за, казалось бы, безобидных изменений - конфигурации, «несвязанных» изменений кода и т. Д.

Лучше явно инициализировать подсистемы на заведомо удачном этапе жизненного цикла программы. Т.е. перетащите [MyClass sharedInstance]; в метод applicationDidFinishLaunching: делегата вашего приложения, если вам действительно нужна эта подсистема, инициализированная на ранней стадии программы (или переместите ее еще раньше, если вы хотите быть более оборонительными).

Еще лучше полностью убрать инициализацию из этого метода. Т.е. [MyClass initializeSharedInstance]; где + sharedInstance asserts (), если этот метод не вызывается первым.

Как бы я ни был поклонником удобства, 25 лет программирования на ObjC научили меня, что ленивая инициализация - это больше проблем с обслуживанием и рефакторингом, чем она того стоит.


Хотя состояние гонки, описанное ниже, существует, этот код не исправляет то, что описано ниже. Так было пару десятилетий, когда мы не беспокоились о параллелизме в инициализаторах общих экземпляров. Оставить неправильный код для процветания.

Имейте в виду, что для правильных ответов Колина и Харальда существует очень тонкое состояние гонки, которое может привести вас в мир горя.

А именно, если -init выделяемого класса вызывает метод sharedInstance , он сделает это до того, как переменная будет установлена. В обоих случаях это приведет к тупику.

Это единственный раз, когда вы хотите разделить выделение памяти и инициализацию. Подделка кода Колина, потому что это лучшее решение (при условии Mac OS X):

+(MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    // partial fix for the "new" concurrency issue
    if (sharedInstance) return sharedInstance;
    // partial because it means that +sharedInstance *may* return an un-initialized instance
    // this is from https://stackoverflow.com/questions/20895214/why-should-we-separate-alloc-and-init-calls-to-avoid-deadlocks-in-objective-c/20895427#20895427

    dispatch_once(&pred, ^{
        sharedInstance = [MyClass alloc];
        sharedInstance = [sharedInstance init];
    });

    return sharedInstance;
}

примечание это работает только в Mac OS X; В частности, X 10.6+ и iOS 4.0+. В более старых операционных системах, где блоки недоступны, используйте блокировку или одно из различных средств для выполнения чего-либо, не основанного на блоках.


Вышеупомянутый шаблон на самом деле не предотвращает проблему, описанную в тексте, и приведет к тупиковой ситуации при ее обнаружении. Проблема в том, что dispatch_once () не является повторно используемым и, таким образом, если init вызывает sharedInstance , город клина .

56
ответ дан 27 November 2019 в 17:31
поделиться

Самый быстрый потокобезопасный способ сделать это - использовать Grand Central Dispatch (libdispatch) и dispatch_once ()

+(MyClass *)sharedInstance
{   
    static MyClass *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        sharedInstance = [[MyClass alloc] init];
    });

    return sharedInstance;
}
38
ответ дан 27 November 2019 в 17:31
поделиться

Эта страница CocoaDev может быть полезна для ваших нужд.

2
ответ дан 27 November 2019 в 17:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: