Issott_Common Design Patterns for Symbian OS-The Foundations of Smartphone Software_0470516356 (779879), страница 67
Текст из файла (страница 67)
Only one call to amethod that can fail is necessary, in order to instantiate the singletoninstance. Upon instantiation, the singleton is guaranteed to be returnedas a reference, so removing the requirement for pointer verification orleave-safe code.356MAPPING WELL-KNOWN PATTERNS ONTO SYMBIAN OSPositive Consequences• Simple to understand and implement.• Little impact on performance.Negative Consequences• Uses 4 KB per process for processes that don’t already load a DLLusing WSD.• Can only be used safely by one thread.Implementation B: TLS Singleton in a Single ThreadConstraints on the Singleton• Must only be used within a single thread.• May be used within an EXE or a DLL on any version of Symbian OS.• WSD may not be used.Thread local storage (TLS) can be used to implement Singleton whereWSD is not supported or recommended.
This implementation can beused in DLLs on all versions of Symbian OS, and in EXEs if desired. It isnot thread-safe.DetailsTLS is a single storage area per thread, one machine word in size (32 bitson Symbian OS v9).20 A pointer to the singleton object is saved to theTLS area and, whenever the data is required, the pointer in TLS is used toaccess it.The operations for accessing TLS are found in class Dll, which isdefined in e32std.h:class Dll{public:static TInt SetTls(TAny* aPtr);static TAny* Tls();static void FreeTls();...};Adapting Implementation A to use TLS does not change the APIof CSingleton, except that the methods to create and access thesingleton, CreateL() and Instance(), must be exported in order to20 Note that, since TLS is provided on a per-thread basis, this implementation does notshare the data between threads.
See Implementations C, D or E for multi-threaded singletons.SINGLETON357be accessible to client code outside the DLL, as must any specific methodsthat the singleton class provides. The implementation of CreateL() andInstance() must be modified as follows:EXPORT_C /*static*/ void CSingleton::CreateL(){ASSERT(!Dll::Tls());if (!Dll::Tls()){ // No singleton instance exists yet so create oneCSingleton* singleton = new(ELeave) CSingleton();CleanupStack::PushL(singleton);singleton->ConstructL();User::LeaveIfError(Dll::SetTls(singleton));CleanupStack::Pop(singleton);}}EXPORT_C /*static*/ CSingleton& CSingleton::Instance(){CSingleton* singleton = static_cast<CSingleton*>(Dll::Tls());ASSERT(singleton);return (*singleton);}Positive Consequences• Relatively straightforward to understand and implement.• Circumvents the WSD limitations in Symbian OS DLLs.Negative Consequences• The price of using TLS instead of direct access to WSD is performance.Data may be retrieved from TLS more slowly than direct access21through a RAM lookup.
On ARMv6 CPU architectures, it is about30 times slower however this should be considerably improved bysubsequent CPU architectures.• Maintainability is reduced because of the need to manage the singleTLS slot per thread. If TLS is used for anything else in the thread,all the TLS data must be put into a single class and accessed asappropriate through the TLS pointer.
This can be difficult to maintain.Implementation C: Classic Thread-safe Singleton Within a ProcessConstraints on the Singleton• Must be used within a single process but may be used across multiplethreads.21 See[Sales, 2005].358MAPPING WELL-KNOWN PATTERNS ONTO SYMBIAN OS• May be used within an EXE on any version of Symbian OS or in a DLLon Symbian OS v9.x, if absolutely necessary.• WSD may be used.DetailsWe earlier demonstrated that the Instance() method of the classicalimplementation of Singleton is not thread-safe – race conditions mayoccur that result in a memory leak by creating two instances of the class.At first sight, there appears to be a trivial solution to the problemof making Singleton leave-safe – add a mutex. However, consider thefollowing pseudocode:/*static*/ Singleton& Singleton::Instance(){Mutex.Lock(); // Pseudocodeif (!pInstance_){pInstance_ = new Singleton();}Mutex.Unlock(); // Pseudocodereturn (*pInstance_);}The use of Mutex has prevented the potential for race conditionsand ensures that only one thread executes the test for the existence ofpInstance_ at any one time.
However, it also means that the mutexmust be locked and unlocked every time the singleton is accessed eventhough the singleton creation race condition we described earlier canonly occur once. The acquisition of the mutex lock, and its release,results in an overhead that is normally unnecessary. The solution mayhave looked straightforward, but the overhead is far from desirable.To work around this, perhaps we could only lock the mutex afterthe test for the existence of pInstance_ ? This gives us the followingpseudocode:/*static*/ Singleton& Singleton::Instance()1 {2 if (!pInstance_)3{4Mutex.Lock(); // Pseudocode5pInstance_ = new Singleton();6Mutex.Unlock(); // Pseudocode7}8 return (*pInstance_);9 }However, this has reintroduced the possibility of a race-conditionand thus a memory leak.
Assume thread A runs and tests pInstance_SINGLETON359in line 2. If it is NULL, the code must lock the mutex and create thesingleton. However, suppose thread A is interrupted, prior to executingline 4, by thread B. The singleton hasn’t been created yet, so thread Bpasses through the if statement and on to line 4, locking the mutexand creating pInstance_, before unlocking the mutex in line 6. Whenthread A runs again, it is from line 4, and it proceeds to lock themutex and create a second instance of the singleton. The check forthe existence of pInstance_ comes too early if it occurs only beforethe lock.This gives rise to an implementation that double checks the pInstance_ pointer: that is, checking it after acquiring the lock as well asbefore.
This is known as the Double-Checked Locking Pattern (DCLP)and was first outlined in [Schmidt and Harrison, 1996]:/*static*/ Singleton& Singleton::Instance()1 {2 if (!pInstance_)3{4Mutex.Lock(); // Pseudocode5if (!pInstance_)6{7pInstance_ = new Singleton();8}9Mutex.Unlock(); // Pseudocode10}11return (*pInstance_);12 }Now the mutex is only acquired if pInstance_ is not yet initialized,which reduces the run-time overhead to the minimum, but the potentialfor a race condition is eliminated, because a check is also performed afterthe mutex is locked. It is these two checks that give rise to the name ofDCLP.Now let’s consider the simplest DCLP implementation in SymbianC++, using WSD.22 As we saw in Implementation A, there are twopossible approaches:• Provide a method to provide access to the singleton instance andcreate it if it does not already exist.• Split the creation of the singleton instance from the method requiredto access it, to make it easier for calling code to use the singleton without having to handle errors when memory resources arelow.22 Please note that the implementation shown is unsafe, as described in the consequencessections.
The DCLP should be used carefully until later versions of Symbian OS provide amemory barrier to guarantee the order of execution on both single- and multiple-processorhardware. Even then, there will still be the problem of using a globally-named mutex, whichis insecure as we highlight in the consequences sections.360MAPPING WELL-KNOWN PATTERNS ONTO SYMBIAN OSThe InstanceL() method for the former approach is as follows:23/*static*/ CSingleton& CSingleton::InstanceL(){if (!iSingletonInstance) // Check to see if the singleton exists{RMutex mutex;// Open a global named RMutex (or RFastLock)User::LeaveIfError(mutex.OpenGlobal(KSingletonMutex));mutex.Wait(); // Lock the mutexif (!iSingletonInstance) // Perform the second check{iSingletonInstance = CSingleton::NewL();}mutex.Signal(); // Unlock the mutex}return (*iSingletonInstance);}A very basic example of how the singleton may be used is shownbelow. Three threads, a main thread and two secondary threads, accessthe singleton, and call a method called DoSomething() to illustrate itsuse:TInt Thread1EntryPoint(TAny* /*aParameters*/){TRAPD(err, CSingleton::InstanceL().DoSomething());...return err;}TInt Thread2EntryPoint(TAny* /*aParameters*/){TRAPD(err, CSingleton::InstanceL().DoSomething());...return err;}// Main (parent) thread code// Creates a named global mutexRMutex mutex;User::LeaveIfError(mutex.CreateGlobal(KSingletonMutex));CleanupClosePushL(mutex);...RThread thread1;User::LeaveIfError(thread1.Create(_L("Thread1"), Thread1EntryPoint,KDefaultStackSize, KMinHeapSize,KMaxHeapSize, NULL));23 Thetwo-phase construction code is as shown in Implementation A.SINGLETON361CleanupClosePushL(thread1);RThread thread2;User::LeaveIfError(thread2.Create(_L("Thread2"), Thread2EntryPoint,KDefaultStackSize, KMinHeapSize,KMaxHeapSize, NULL));CleanupClosePushL(thread2);// Start the threads off ’at the same time’thread1.Resume();thread2.Resume();TRAPD(err, CSingleton::InstanceL().DoSomething());// Not shown here: Don’t forget to clean up the threads, the mutex and// the singleton...The alternative approach described above splits creation of the singleton from access to it, to make it easier for calling code to use.With the addition of DCLP, the code for this approach looks as follows:*static*/ CSingleton& CSingleton::Instance(){ASSERT(iSingletonInstance);return (*iSingletonInstance);}/*static*/ void CSingleton::CreateL(){if (!iSingletonInstance) // Check to see if the singleton exists{RMutex mutex;User::LeaveIfError(mutex.OpenGlobal(KSingletonMutex));mutex.Wait(); // Lock the mutexif (!iSingletonInstance) // Second check{iSingletonInstance = CSingleton::NewL();}mutex.Signal(); // Unlock the mutex}}The code that uses the singleton is now more straightforward, sincethe Instance() method always returns a valid instance and no errorshave to be handled.