Singleton pattern: Difference between revisions

From iPhone Development Wiki
No edit summary
mNo edit summary
Line 1: Line 1:
The traditional singleton creation
The traditional singleton creation
<source lang="objc">
<source lang="objc">
+(XXClass*)sharedInstance {
+ (XXXClass *)sharedInstance {
   static XXClass* XXClass_sharedInst = nil;
   static XXXClass* XXXClass_sharedInst = nil;
   @synchronized(self) {
   @synchronized(self) {
     if (XXClass_sharedInst == nil) {
     if (XXXClass_sharedInst == nil) {
       XXInitialize(&XXClass_sharedInst);
       XXXInitialize(&XXXClass_sharedInst);
     }  
     }  
   }
   }
Line 13: Line 13:
is extremely inefficient because <tt>@synchronized</tt> not only lock a mutex, but also insert an exception handler (try/catch block). If you don't care about exceptions and willing to use C functions, [http://www.opengroup.org/onlinepubs/007908799/xsh/pthread_once.html <tt>pthread_once()</tt>] is a better alternative:
is extremely inefficient because <tt>@synchronized</tt> not only lock a mutex, but also insert an exception handler (try/catch block). If you don't care about exceptions and willing to use C functions, [http://www.opengroup.org/onlinepubs/007908799/xsh/pthread_once.html <tt>pthread_once()</tt>] is a better alternative:
<source lang="objc">
<source lang="objc">
static XXClass* XXClass_sharedInst = nil;
static XXXClass *XXXClass_sharedInst = nil;
static pthread_once_t XXClass_onceControl = PTHREAD_ONCE_INIT;
static pthread_once_t XXXClass_onceControl = PTHREAD_ONCE_INIT;
static void XXInitializeOnce(void) { XXInitialize(&XXClass_sharedInst); }
static void XXXInitializeOnce(void) { XXXInitialize(&XXXClass_sharedInst); }
...
...
+(XXClass*)sharedInstance {
+ (XXXClass *)sharedInstance {
   pthread_once(&XXClass_onceControl, &XXInitializeOnce);
   pthread_once(&XXXClass_onceControl, &XXXInitializeOnce);
   return XXClass_sharedInst;
   return XXXClass_sharedInst;
}
}
</source>
</source>
Line 26: Line 26:
If it is safe to create multiple copies of the singleton and destroy the extra ones, you may even use [http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man3/atomic.3.html CAS]:
If it is safe to create multiple copies of the singleton and destroy the extra ones, you may even use [http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man3/atomic.3.html CAS]:
<source lang="objc">
<source lang="objc">
+(XXClass*)sharedInstance {
+ (XXXClass *)sharedInstance {
   static XXClass* XXClass_sharedInst = nil;
   static XXXClass* XXXClass_sharedInst = nil;
   if (XXClass_sharedInst == nil) {
   if (XXXClass_sharedInst == nil) {
     XXClass* tmp;
     XXXClass* tmp;
     XXInitialize(&tmp);
     XXXInitialize(&tmp);
     if (!OSAtomicCompareAndSwapPtrBarrier(nil, tmp, (void*volatile*)&XXClass_sharedInst)))
     if (!OSAtomicCompareAndSwapPtrBarrier(nil, tmp, (void*volatile*)&XXXClass_sharedInst)))
       XXDestroy(tmp);
       XXXDestroy(tmp);
   }
   }
   return XXClass_sharedInst;
   return XXXClass_sharedInst;
}
}
</source>
</source>

Revision as of 00:15, 9 October 2013

The traditional singleton creation

+ (XXXClass *)sharedInstance {
  static XXXClass* XXXClass_sharedInst = nil;
  @synchronized(self) {
    if (XXXClass_sharedInst == nil) {
      XXXInitialize(&XXXClass_sharedInst);
    } 
  }
  return __sharedInst;
}

is extremely inefficient because @synchronized not only lock a mutex, but also insert an exception handler (try/catch block). If you don't care about exceptions and willing to use C functions, pthread_once() is a better alternative:

static XXXClass *XXXClass_sharedInst = nil;
static pthread_once_t XXXClass_onceControl = PTHREAD_ONCE_INIT;
static void XXXInitializeOnce(void) { XXXInitialize(&XXXClass_sharedInst); }
...
+ (XXXClass *)sharedInstance {
  pthread_once(&XXXClass_onceControl, &XXXInitializeOnce);
  return XXXClass_sharedInst;
}

pthread_once() is implemented using a spin lock (which leads to a syscall_thread_switch() kernel call in the worst case).

If it is safe to create multiple copies of the singleton and destroy the extra ones, you may even use CAS:

+ (XXXClass *)sharedInstance {
  static XXXClass* XXXClass_sharedInst = nil;
  if (XXXClass_sharedInst == nil) {
    XXXClass* tmp;
    XXXInitialize(&tmp);
    if (!OSAtomicCompareAndSwapPtrBarrier(nil, tmp, (void*volatile*)&XXXClass_sharedInst)))
      XXXDestroy(tmp);
  }
  return XXXClass_sharedInst;
}

Unfortunately, OSAtomicCompareAndSwapPtrBarrier() on ARMv4 is implemented using a global spin lock, so in principle this is nowhere faster than pthread_once(), and is even more error-prone. On ARMv6 it is implemented as a LDREX/STREX pair[1]:

ENTRY_POINT(_OSAtomicCompareAndSwapPtrBarrier)
1:	ldrex	r3, [r2]	// load existing value and tag memory
	teq	r3, r0		// is it the same as oldValue?
	movne	r0, #0		// if not, return 0 immediately
	bxne	lr	    
	strex	r3, r1, [r2]	// otherwise, try to store new value
	cmp	r3, #0		// check if the store succeeded
	bne	1b		// if not, try again
	mov	r0, #1		// return true
	bx	lr

References