Is dispatch_once overkill inside of +[NSObject initialize]?
concurrency, grand-central-dispatch, ios, objective-c, objective-c-runtime
Solution
Bill Bumgarner says that `dispatch_once` is Apple's recommended practice now.
Relating to thread and memory-safety of `+initialize`, thanks to this tweet, I found the relevant runtime sources to check. `objc-initialize.mm` says:
* Only one thread is allowed to actually initialize a class and send
* +initialize. Enforced by allowing only one thread to set CLS_INITIALIZING.
Classes may be initialized on different threads, and `objc-initialize.mm` has a strategy to avoid them deadlocking:
* +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.
Additionally, class initialization state variables, are guarded by a `monitor_t`, which is actually defined as:
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
} monitor_t;
Since it is a `p_thread_mutex`, and p_thread calls implement memory barriers, it is equally safe to use:
static NSObject * Bar;
@implementation Foo
+ (void)initialize {
if (self == [Foo class]) {
Bar = [NSObject new];
}
}
@end
and
static NSObject * Bar;
@implementation Foo
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Bar = [NSObject new];
});
}
@end
Problem
If I create a singleton inside of `+[NSObject initialize]`, do I need to put my code inside a `dispatch_once` block like so? ``` static NSObject * Bar; @implementation Foo + (void)initialize { if (self == [Foo class]) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Bar = [NSObject new]; }); } } @end ``` EDIT I'm concerned about this because I want to make sure that all threads will see that I've set `Bar` after `+[Foo initialize]` is called. The documentation says `+[NSObject initialize]` is thread-safe, but does that imply it is memory-safe?