【问题标题】:Is dispatch_once overkill inside of +[NSObject initialize]?+[NSObject 初始化] 中的 dispatch_once 是否过度杀伤?
【发布时间】:2013-09-21 01:46:07
【问题描述】:

如果我在 +[NSObject initialize] 中创建一个单例,我是否需要像这样将我的代码放在 dispatch_once 块中?

static NSObject * Bar;
@implementation Foo
+ (void)initialize {
  if (self == [Foo class]) {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      Bar = [NSObject new];
    });
  }
}
@end

编辑

我对此感到担忧,因为我想确保在调用 +[Foo initialize] 之后,所有线程都会看到我设置了 Bar。文档说+[NSObject initialize] 是线程安全的,但这是否意味着它是内存安全的?

【问题讨论】:

  • 我建议将您的逻辑保留为需要dispatch_once() 并使用它。它更直接,并且在重构代码期间复制/粘贴不会破坏它。
  • 我同意。这就是我现在所做的。

标签: ios objective-c concurrency grand-central-dispatch objective-c-runtime


【解决方案1】:

The docs 提到initialize 方法每个类只以线程安全的方式调用一次。因此dispatch_once 是不必要的。

运行时向程序中的每个类发送一个初始化 就在类或从它继承的任何类之前的时间 从程序内部发送了它的第一条消息。 (因此该方法可能 如果该类未被使用,则永远不会被调用。)运行时发送 以线程安全的方式将消息初始化到类。超类 在其子类之前收到此消息。

编辑

正如@Vincent Gable 在 cmets 中提到的,如果Foo 的子类本身没有实现initialize 方法,则initialize 方法可能会被多次调用。但是,这样的调用不会有问题,因为有一个self == [Foo class] 检查。

【讨论】:

  • 不完全,+initialize 有时会发生不止一次“……如果一个子类没有实现 +initialize 但它的超类实现了,那么该超类的 +initialize 将在每个未实现的子类中调用一次,并且一次为自己。” friday.com/bbum/2009/09/06/…
  • 但是内存安全呢?是否保证所有线程都能看到+[Foo initialize]Bar 所做的更改?
  • 根据文档:“运行时将initialize 发送到程序中的每个类,恰好在该类或从它继承的任何类从程序中发送其第一条消息之前。”采取最偏执的解释,我可以看到所有线程在阅读之前可能没有看到对全局Bar 的更改。一个简单的解决方案是将Bar 从全局移动到通过方法访问的单例——这样就必须发送一条消息来访问它,保证它在任何人都可以读取之前已经被初始化。
【解决方案2】:

您的直接问题的答案是您不需要dispatch_once,但您确实需要您在其中的类检查,因为+initialize 将被称为@987654321 @。它只会为您关心的特定类(Foo)调用一次,因此dispatch_once 是无关的。回复:线程安全,+initialize 方法将在任何其他方法被分派到类(或其实例)之前完成。

但是,您没有描述所需的访问模式,因此根据您的需要,您可能希望做相反的事情——如果您希望子类也可以访问 Bar,那么这将是脆弱的;如果子类在Foo 本身之前被初始化,那么类检查将阻止Bar 被创建。如果您想要这种行为,请使用 dispatch_once 但删除类检查——这通常允许在第一次 Foo 或其任何子类初始化时创建 Bar。 (警告:除非是子类also overrides +initialize, of course。)

【讨论】:

  • 我实际上更喜欢检查if (Bar == nil) 而不是self == [Foo class],但我认为这归结为风格。
  • self == [Foo class] 是所有+initialize 使用的正确模式。检查其他内容,例如 Bar==nil 是适用于此代码的特殊情况,但不是一般模式。我会推荐标准类检查。
  • 但是内存安全呢?是否保证所有线程都能看到 +[Foo initialize] 对 Bar 所做的更改?
  • @HeathBorders:是的,但听起来这里还有另一种微妙的行为,尤其是在子类化方面。我更新了答案,希望对您有所帮助。
【解决方案3】:

Bill Bumgarner 表示dispatch_once is Apple's recommended practice now

关于+initialize 的线程和内存安全,感谢this tweet,我找到了relevant runtime sources 进行检查。 objc-initialize.mm 说:

 * Only one thread is allowed to actually initialize a class and send 
 * +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.

类可以在不同的线程上初始化,objc-initialize.mm 有一个策略来避免它们死锁:

*  +initialize deadlock case when a class is marked initializing while 
 *  its superclass is initialized. Solved by completely initializing 
 *  superclasses before beginning to initialize a class.
 *
 *  OmniWeb class hierarchy:
 *                 OBObject 
 *                     |    ` OBPostLoader
 *                 OFObject
 *                 /     \
 *      OWAddressEntry  OWController
 *                        | 
 *                      OWConsoleController
 *
 *  Thread 1 (evil testing thread):
 *    initialize OWAddressEntry
 *    super init OFObject
 *    super init OBObject            
 *    [OBObject initialize] runs OBPostLoader, which inits lots of classes...
 *    initialize OWConsoleController
 *    super init OWController - wait for Thread 2 to finish OWController init
 *
 *  Thread 2 (normal OmniWeb thread):
 *    initialize OWController
 *    super init OFObject - wait for Thread 1 to finish OFObject init
 *
 *  deadlock!
 *
 *  Solution: fully initialize super classes before beginning to initialize 
 *  a subclass. Then the initializing+initialized part of the class hierarchy
 *  will be a contiguous subtree starting at the root, so other threads 
 *  can't jump into the middle between two initializing classes, and we won't 
 *  get stuck while a superclass waits for its subclass which waits for the 
 *  superclass.

此外,类初始化状态变量,由monitor_twhich is actually defined as 保护:

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
} monitor_t;

既然是p_thread_mutexp_thread calls implement memory barriers,使用起来同样安全:

static NSObject * Bar;
@implementation Foo
+ (void)initialize {
  if (self == [Foo class]) {
    Bar = [NSObject new];
  }
}
@end

static NSObject * Bar;
@implementation Foo
+ (void)initialize {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Bar = [NSObject new];
  });
}
@end

【讨论】:

  • 因为您列出的所有内容,所以无疑是最快的说+ (void)initialize { static BOOL initialized = NO; if (!initialized) { /* initialize here */ initialized = YES; } }
  • 此外,在 armv7s 和 arm64 上执行的微基准测试表明 if (self == [Foo class]) 优于 dispatch_once(),但差异为
猜你喜欢
  • 2011-06-24
  • 2010-10-24
  • 1970-01-01
  • 2013-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多