【问题标题】:Thread safe instantiation of a singleton单例的线程安全实例化
【发布时间】:2011-01-13 00:50:48
【问题描述】:

使用哪一种同步方法来确保单例仍然是单例?

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

嗯.. 有这方面的 cmets 吗?

【问题讨论】:

  • 您可能有兴趣阅读这篇文章 (steve.yegge.googlepages.com/singleton-considered-stupid)。
  • 尽管 yegge 讨厌单身人士,但他们在 iPhone 上肯定是有目的的。但是,如果您只是创建一个“命名空间”,请改用类方法。
  • @bentford - 我已经为 iPhone 编程 4 年了,旧代码和新代码。我见过一次使用单例(在一些非常糟糕的代码中)。
  • @HotLicks 我几乎在所有项目中都使用 1 或 2 个单例。两个用例是:1) 处理长时间运行的网络操作和 2) 管理 CoreData 的全局持久上下文。

标签: objective-c multithreading memory-management thread-safety singleton


【解决方案1】:

确保您也阅读了有关此问题/答案的讨论。 Why should we separate alloc and init calls to avoid deadlocks in Objective-C?


扩展竞争条件问题; 真正的 修复是在您的应用程序中没有不确定的初始化。 Indeterminate 或 lazy 初始化会导致行为由于看似无害的更改(配置、“不相关”的代码更改等...

最好在程序生命周期中的已知良好点显式初始化子系统。 IE。如果您真的需要在程序的早期初始化子系统(或者如果您想更加防御,甚至更早地移动它),请将 [MyClass sharedInstance]; 放入您的 App 委托的 applicationDidFinishLaunching: 方法中。

最好还是将初始化完全移出该方法。 IE。 [MyClass initializeSharedInstance]; where +sharedInstance asserts() 如果没有首先调用该方法。

尽管我很喜欢方便,但 25 年的 ObjC 编程经验告诉我,延迟初始化会导致更多的维护和重构问题,而不是值得。


虽然存在下面描述的竞争条件,但此代码不能解决下面描述的问题。几十年来,当我们不担心共享实例初始化器中的并发性时,它确实如此。为繁荣留下错误的代码。

请记住,对于 Colin 和 Harald 的其他正确答案,存在一个非常微妙的竞争条件,可能会导致您陷入困境。

也就是说,如果被分配的类的-init碰巧调用了sharedInstance方法,它会在设置变量之前这样做。在这两种情况下都会导致死锁。

这是您想要分离 alloc 和 init 的一次。抄袭 Colin 的代码,因为它是最好的解决方案(假设 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,楔形城市。

【讨论】:

  • @bbum,(1) 为什么-init 会打电话给+sharedInstance,以及(2) 为什么要包含if (sharedInstance) return sharedInstance; 这一行?
  • 仅出于完整性考虑 - 当 iOS 上引入块时,上述内容适用于 iOS 4 及更高版本。
  • 我建议删除“if(sharedInstance) return sharedInstance”,它是双重检查锁定并且不起作用(参见aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf)
  • @MattDiPasquale 差不多。我还在想这个;我想围绕调度的条件可能会有所帮助,但在这条道路上存在潜在的疯狂。
  • 您为什么编辑您的答案以删除使其有价值的单个信息?您编辑的代码与 Colin 的原始答案没有什么不同,现在您的贡献毫无意义,您的代码不再与您的文本相对应(您的答案所涉及的 alloc 和 init 之间的分隔在哪里?)。如果事后认为您的答案无效,那么 (1) 请添加注释说明,而不是让您的答案不一致,并且 (2) 请解释为什么它会无效。谢谢!
【解决方案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;
}

【讨论】:

  • 为什么必须使用dispatch_once?为什么你不能只做这样的 if 语句:“ if (sharedInstance == nil) { sharedInstance = [MyClass alloc] init];”
  • 这并不能保证它只会被分配一次。 dispatch once 保证块只会被执行一次,即使多个线程都同时调用相同的代码。在您的代码中,一个线程可以执行 if 语句,然后开始分配实例,然后另一个线程同时评估 if 语句,然后分配第二个实例,现在繁荣您有无法回收的泄漏内存.您的代码可以正常运行,但它无法处理我刚才描述的情况。
  • 谢谢我明白你关于线程安全的观点。我不明白为什么只使用 dispatch_once 不够?为什么我仍然看到人们做 if 语句 + dispatch_once + static + 类方法?如果您使用的是 dispatch_once 那么为什么要指定静态?
  • stackoverflow.com/questions/4576607/… 我没有使用 if 语句,因为 dispatch_once 只在不必要的时候执行。 static 确保变量将在该方法的多次执行中存在,否则在该方法的第二次执行时,我们将获得一个新指针,该指针不指向我们第一次分配的内存,并且该内存将被泄漏。类方法通常用于显示您正在处理一个共享实例,例如 NSFileManager.defaultManager()
【解决方案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 page 可以满足您的需要。

    【讨论】:

    • 谢谢。嗯..我没有考虑保留或过度释放单身人士的可能性。见鬼,谁会做这种事? :)
    【解决方案5】:

    如果有人关心,这里有另一个宏用于同一件事:)

    恕我直言,与the other variations 相比,它提供了更大的灵活性。

    #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 compound expressions 扩展,Clang 也支持,C99 variadic macros support。

    经过预处理后,输出将如下所示(您可以通过在 Xcode 5 中调用 Product &gt; Perform Action &gt; Preprocess "YourClassName.m" 自行测试):

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多