Потокобезопасная реализация экземпляра синглтона - PullRequest
33 голосов
/ 04 февраля 2010

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

+(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;
}

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

Ответы [ 5 ]

56 голосов
/ 04 февраля 2010

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

<Ч />

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

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

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

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

<Ч />

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

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

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

Это один раз, когда вы хотите разделить alloc и init. Подбирайте код Колина, потому что это лучшее решение (при условии 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;
}

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

<Ч />

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

38 голосов
/ 04 февраля 2010

Самый быстрый потокобезопасный способ сделать это с помощью 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;
}
12 голосов
/ 18 ноября 2011

Если кому-то все равно, вот макрос для того же:

   /*!
    * @function Singleton GCD Macro
    */
    #ifndef SINGLETON_GCD
    #define SINGLETON_GCD(classname)                            \
                                                                \
    + (classname *)shared##classname {                          \
                                                                \
        static dispatch_once_t pred;                            \
        static classname * shared##classname = nil;             \
        dispatch_once( &pred, ^{                                \
            shared##classname = [[self alloc] init];            \
        });                                                     \
        return shared##classname;                               \
    }                                                           
    #endif
2 голосов
/ 04 февраля 2010

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

1 голос
/ 08 января 2014

Если кому-то все равно, вот еще один макрос для той же вещи:)

ИМХО, он обеспечивает большую гибкость по сравнению с другими вариантами .

#define SHARED_INSTANCE(...) ({\
    static dispatch_once_t pred;\
    static id sharedObject;\
    dispatch_once(&pred, ^{\
        sharedObject = (__VA_ARGS__);\
    });\
    sharedObject;\
})

Использование, однострочная инициализация:

+ (instancetype) sharedInstanceOneLine {
    return SHARED_INSTANCE( [[self alloc] init] );
}

Использование, многострочная инициализация (обратите внимание на фигурные скобки вокруг блока кода):

+ (instancetype) sharedInstanceMultiLine {
    return SHARED_INSTANCE({
        NSLog(@"creating shared instance");
        CGFloat someValue = 84 / 2.0f;
        [[self alloc] initWithSomeValue:someValue]; // no return statement
    });
}

Использование в правой части назначения:

- (void) someMethod {
    MethodPrivateHelper *helper = SHARED_INSTANCE( [[MethodPrivateHelper alloc] init] );
    // do smth with the helper
}
// someMethod should not call itself to avoid deadlock, see bbum's answer

В этой модификации используются две языковые возможности: расширение составных выражений GCC , которое также поддерживается Clang, и поддержка макроса C99 с переменными макросами .

После предварительной обработки выходные данные будут выглядеть (вы можете проверить это самостоятельно, вызвав Product > Perform Action > Preprocess "YourClassName.m" в Xcode 5):

+ (instancetype) sharedInstanceOneLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[self alloc] init] );
        });
        sharedObject; // this object will be returned from the block
    });
}

+ (instancetype) sharedInstanceMultiLine {
    return ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ({
                NSLog(@"creating shared instance");
                CGFloat someValue = 84 / 2.0f;
                [[self alloc] initWithSomeValue:someValue];
            });
        });
        sharedObject;
    });
}

- (void) someMethod {
    MethodPrivateHelper *helper = ({
        static dispatch_once_t pred;
        static id sharedObject;
        dispatch_once(&pred, ^{
            sharedObject = ( [[MethodPrivateHelper alloc] init] );
        });
        sharedObject;
    });
}
...