Подтвердить что ты не робот

Реализация потокового сейсмического синглета

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

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

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

4b9b3361

Ответ 1

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


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

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

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

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


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

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

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

Это один раз, когда вы хотите отделить alloc и init. Cribbing Colin code, потому что это лучшее решение (предполагая 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 /questions/162483/why-should-we-separate-alloc-and-init-calls-to-avoid-deadlocks-in-objective-c/931419#931419

    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, город клина.

Ответ 2

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

Ответ 3

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

   /*!
    * @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

Ответ 5

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

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

#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 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;
    });
}