Реализация потокового сейсмического синглета
Какой один метод синхронизации для использования одного синглтона остается одиночным?
+(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;
}
Хммм.. любые комментарии по этому поводу?
Ответы
Ответ 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
Ответ 4
Эта страница CocoaDev может быть полезна для ваших нужд.
Ответ 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;
});
}