IT tutorials
 
Mobile
 

Mapping Well-Known Patterns onto Symbian OS : Singleton

12/8/2011 3:58:36 PM
- Free product key for windows 10
- Free Product Key for Microsoft office 365
- Malwarebytes Premium 3.7.1 Serial Keys (LifeTime) 2019

1. Problem

1.1. Context

You wish to 'ensure a class only has one instance and provide a global point of access to it.' [Gamma et al., 1994]

1.2. Summary
  • A single instance of a class is required by a component or even the whole system. For example, to provide a single point of access to a physical device, such as a camera, or to a resource or resource pool, such as a logging object, a thread or a block of memory.[]

    [] See Pooled Allocation [Weir and Noble, 2000].

  • A way of synchronizing access to the single instance may be needed if you wish it to be in scope for different parts of a system and used by several threads or processes.

  • Instantiation of the object should controllable. Instantiation should either be deferred until the first time it is needed, or the object should be instantiated in advance of other objects, to ensure a particular order of initialization.

1.3. Description

This pattern is one of the simplest design patterns and arguably the most popular pattern found in [Gamma et al., 1994]. Hence in this discussion, rather than examine the pattern in itself, we place emphasis on how to implement it on Symbian OS, covering a range of different circumstances. For detailed discussion of the pattern itself covering, for example, the overall consequences of the pattern or how to subclass a singleton class, please see [Gamma et al., 1994].

The pattern involves just one class that provides a global point of access to a single instance, which instantiates itself, either using Immortal (see page 53) or Lazy Allocation (see page 63). However, there is a basic problem for developers working on Symbian OS, which is its incomplete support for writable static data in DLLs. To explain the issue, we must digress briefly: first of all, to discuss what we mean by 'incomplete support for writable static data' and then to explain why it's a big deal.

The Writable Static Data Restriction on Symbian OS

For the purposes of this discussion, writable static data (WSD) is any modifiable globally-scoped variable declared outside of a function and any function-scoped static variable.

WSD was not allowed in DLLs built to run on target devices based on Symbian OS v8.1a or earlier.[] For an explanation of how this limitation came about, see [Willee, Nov 2007] and [Stichbury, 2004]. WSD has always been allowed in EXEs but, prior to Symbian OS v9, this was of little help to application developers because applications were built as DLLs.

[] See Table 1 for the corresponding versions of S60 and UIQ.

With the advent of Symbian OS v9, the situation changed for the better. The use of WSD in target DLLs is now possible. However, because it can be expensive in terms of memory usage, it is not recommended when writing a shared DLL that is intended to be loaded into a number of processes. This is because WSD in a DLL typically consumes 4 KB of RAM for each process which loads the DLL.

When a process loads its first DLL containing WSD, it creates a single chunk to store the WSD. The minimum size of a chunk is 4 KB, since this is the smallest possible page size, so at least that amount of memory is consumed, irrespective of how much static data is actually required. Any memory not used for WSD is wasted.[]

[] However, if subsequent DLLs are loaded into the same process and also use WSD, the same chunk is used, rather than a separate chunk (at least another 4 KB) for every DLL.

Since the memory is per process, the potential memory wastage is:[]

[] Assuming you don't use more than 4 KB of WSD.

(4 KB − WSD bytes) × number of client processes

If, for example, a DLL is used by four processes, that's potentially an 'invisible' cost of almost 16 KB since the memory occupied by the WSD itself is usually in the order of bytes.

Furthermore, DLLs that use WSD are not fully supported by the Symbian OS emulator that runs on Windows. Because of the way the emulator is implemented on top of Windows, it can only load a DLL with WSD into a single emulated process. If a second emulated process attempts to load the same DLL on the emulator, it will fail with KErrNot-Supported.[]

[] Symbian OS v9.4 introduces a workaround (see [Willee, Nov 2007] for a more advanced discussion).

There are a number of occasions where the benefits of using WSD in a DLL outweigh the disadvantages; for example, when porting code that uses WSD significantly and for DLLs that will only ever be loaded into one process. As a third-party developer, you may find the memory costs of WSD acceptable if you create a DLL that is intended only to be loaded into a single application. However, if you are working in device creation, perhaps creating a shared DLL that ships within Symbian OS, one of the UI platforms, or on a phone handset, the trade-offs are different. Your DLL may be used by a number of processes and the memory costs and constraints are such that using WSD is less likely to be justifiable.

Application developers are no longer affected as severely by the limitation, because a change in the application framework architecture in Symbian OS v9 means that all applications are now EXEs rather than DLLs. Writable static data has always been allowed in EXEs built for target devices, so an application developer can use WSD if it is required. Table 1 summarizes the support for WSD in DLLs and applications on various versions of the Symbian UI platforms.

Table 1. WSD Support Across Versions of Symbian OS and GUI Variants
UI PlatformSymbian OS versionWSD allowed in DLLs?WSD allowed in applications?
UIQ 2.xv7.0NoNo
S60 1st and 2nd Editionsv6.1, v7.0s, v8.0a, v8.1a
UIQ 3v9.xYes, but not recommended due to the limitations on the Windows emulator and potentially large memory consumption.Yes, since applications are EXEs.
S60 3rd Edition

Why Is the Limitation on WSD a Problem when Implementing Singleton?

Figure 1 shows the UML diagram for a typical Singleton.

The classic implementation of the Singleton pattern in C++ uses WSD, as shown below:

class Singleton
{
public:
static Singleton* Instance();
// Operations supplied by Singleton

private:
Singleton(); // Implementation omitted for clarity
~Singleton(); // Implementation omitted for clarity
private:
static Singleton* pInstance_; // WSD
};

/*static*/ Singleton* Singleton::pInstance_ = NULL;
/*static*/ Singleton* Singleton::Instance()
{
if (!pInstance_)
pInstance_ = new Singleton;

return (pInstance_);
}

Figure 1. UML diagram for a typical Singleton

Because of the constraint on the use of WSD, the classic implementation of Singleton cannot be used in DLLs before Symbian OS v9.[] An alternative implementation, which we'll discuss shortly, must be used instead. This alternative is also recommended when writing DLLs for newer versions of Symbian OS that do allow WSD in DLLs, simply to avoid unnecessary waste of memory and to enable complete testing on the Windows emulator.

[] Rather confusingly, DLL code that used WSD did actually build and run on the Windows emulator on versions of Symbian OS that didn't support the use of WSD in DLLs. It was only when the same code was compiled for target hardware that the use of WSD was actually flagged as an error – a time-consuming irritant for those working primarily on the emulator to build and debug in the early stages of a project. You can find out how to track down WSD if you're not using it deliberately in FAQ 0329 on the Symbian Developer Network (developer.symbian.com/main/support/faqs).

However, let's have another brief digression and discuss the classic code above. As you can see, the singleton object effectively owns and creates itself. The code required to instantiate Singleton is private, so client code cannot create the singleton instance directly. Only the static function Instance(), which is a member of the class, can create pInstance_ and it does so the first time that it is called. This approach guarantees the singularity of the object, in effect, enforcing it at compile time, and also delays the construction until the object is needed, which is known as Lazy Allocation (see page 63). Using that pattern can be advantageous if an object is 'expensive' to instantiate and may not be used. A good example is a logging class that is only used if an error occurs when the code is running. There is no point in initializing and holding open a resource such as a file server session if it is not needed when the code logic runs normally.

Hazards of Singleton

This pattern is often embraced for its simplicity by those new to design patterns, but it presents a number of potential pitfalls to the unwary programmer, especially the cleanup and thread-safety of the singleton class itself.

For example, we've just discussed the construction of Singleton, but what about the other end of its lifetime? Note that the destructor is private[] to prevent callers of Instance() from deleting pInstance_ accidentally or on purpose. However, this does raise some interesting questions: When should the singleton instance destroy itself? How do we de-allocate memory and release resource handles? [Alexandrescu, 2001] is a good place to find out more and we'll discuss it in the Solution section.

[] If the Singleton class is ever intended to be sub-classed, it should be made protected.

Also, how do we ensure that the implementation is thread-safe if the singleton instance needs to be accessed by more than one thread? After all, it's intended to be used as a shared global resource, so it's quite possible that multiple threads could simultaneously attempt to create or access the singleton. The classical implementation we examined earlier is not thread-safe, because race conditions are possible when separate threads attempt to create the singleton. At worst, a memory leak arises if the first request for the singleton by one thread is interrupted by the thread scheduler and another thread then runs and also makes a request for the singleton object.

Here again is the code for Instance(), with line numbers to aid the discussion:

1 /*static*/ Singleton* Singleton::Instance()
2 {
3 if (!pInstance_)
4 {
5 pInstance_ = new Singleton;
6 }
7 return (pInstance_);
8 }

Consider the following scenario: Thread A runs and calls Instance(). The singleton pInstance_ has not yet been created, so the code runs on to line 5, but it is then interrupted by the thread scheduler before pInstance_ can be created. Thread B starts executing, also calls Instance() and receives the same result as thread A when it tests the pInstance_ pointer. Thread B runs to line 5, creates the singleton instance and continues successfully. However, when Thread A resumes, it runs from line 5, and also instantiates a Singleton object, assigning it to pInstance_. Suddenly there are two Singleton objects when there should be just one, causing a memory leak because the one pInstance_ pointer references only the latter object.

When Singleton is used with multiple threads, race conditions clearly need to be avoided and we'll discuss how to do this in the Solution section below. [Myers and Alexandrescu, 2004] provides an even more detailed analysis.

Should You Use Singleton?

Be wary of just reaching for this pattern as there are a number of subtleties that can catch you out later. This pattern increases the coupling of your classes by making the singleton accessible 'everywhere', as well as making unit testing more difficult as a singleton carries its state with it as long as your program lasts. There are a number of articles that discuss this in more depth but here are just a few:

  • Do we really need singletons? [Saumont, 2007]

  • Use your singletons wisely [Rainsberger, 2001]

  • Why singletons are evil [Densmore, 2004]

Synopsis

We've covered quite a lot of ground already, so let's summarize the problems that we need to address in the solution:

  • Symbian-specific issues

    • The classic implementation of Singleton does not allow for potential out-of-memory conditions or two-phase construction. What is the preferred approach in Symbian C++?

    • Singleton cannot be implemented using the classic solution, which depends on WSD, where WSD is not supported (in DLLs in Symbian OS v8.1a or earlier) or is not recommended (DLLs in Symbian OS v9). How can you work around this?

  • General issues

    • When should a singleton instance destroy itself?

    • How should an implementation of Singleton cope with access by multiple threads?

1.4. Example

On Symbian OS, the window server (WSERV) provides a low-level API to the system's user interface devices – screens, keyboard and pointer. The API is extensive and complex and would make most application development unnecessarily convoluted if used directly. For example, to receive events caused by the end user interacting with a list box, you would need to use a window server session, as well as Active Objects (see page 133), to handle asynchronous events raised by WSERV in response to input from the end user.

The control framework (CONE) hides much of this complexity and provides a simplified API that meets the requirements of most applications. CONE encapsulates the use of Active Objects (see page 133) and simplifies the interaction with the Window Server by providing a single, high-level class, CCoeEnv, for use by application programmers. CCoeEnv also provides simplified access to drawing functions, fonts, and resource files which are used by many applications. The class is also responsible for creation of the cleanup stack for the application, for opening sessions to the window server, file server, screen device, application's window group and a graphics context.

Only one instance of CCoeEnv is needed per thread since it provides thread-specific information. However, CCoeEnv is intended for use by a potentially large number of different classes within an application, so the instance of CCoeEnv should be globally accessible within each thread.

2. Solution

Depending on the scope across which you require the singleton to be unique there are a number of different ways of working around the problem as shown in Table 9.3.

Some of the implementations discussed are accompanied by their own issues. For example, at the time of writing, Implementation C should be regarded as illustrative rather than recommended on Symbian OS.

2.1. Implementation A: Classic Singleton in a Single Thread

Constraints on the Singleton

  • Must only be used within a single thread.

  • Can be used within an EXE on any version of Symbian OS or in a DLL on Symbian OS v9.x if absolutely necessary.

  • WSD may be used.

Table 2. Synopsis of Solutions
ImplementationMaximum Required Scope for the SingletonDetailsRequires WSD?
AWithin a GUI application or other EXEs such as console applications or serversImplement normally with only minor modification for Symbian C++.Yes
BWithin a DLL for a specific threadUse thread local storage (TLS).No
CAcross multiple threadsUse a thread synchronization mechanism to prevent race conditions.Yes
DAcross multiple threadsManage initialization and access to the TLS data as each secondary thread starts running.No
EAcross multiple processesUse the Symbian OS client–server framework.No

Details

On Symbian OS, the implementation of Singleton must take into account the possibility of instantiation failure. For example, a leave because there is insufficient memory to allocate the singleton instance. One possible technique is to implement a standard NewL() factory function that will be familiar to Symbian C++ developers:

class CSingleton : public CBase
{
public:
// To access the singleton instance
static CSingleton& InstanceL();
private: // The implementations of the following are omitted for clarity
CSingleton();
~CSingleton();
static CSingleton* NewL();
void ConstructL();
private:
static CSingleton* iSingletonInstance; // WSD
};

/*static*/ CSingleton& CSingleton::InstanceL()
{
if (!iSingletonInstance)
{
iSingletonInstance = CSingleton::NewL();
}
return (*iSingletonInstance);
}

Inside InstanceL(), the NewL() factory method is called to create the instance. The main issue is what to do if this fails? One option would be to ignore any errors and pass back a pointer to the singleton that might be NULL. However, this puts the burden on clients to check for it being NULL. They are likely to forget at some point and get a KERN-EXEC 3 panic. Alternatively, we could panic directly if we fail to allocate the singleton which at least tells us specifically what went wrong but still takes the whole thread down which is unlikely to be the desired outcome. The only reasonable approach is to use Escalate Errors (see page 32) and leave (see Figure 2). This means the client would have to deliberately trap the function and willfully ignore the error to get a panic. While this is entirely possible, it won't happen by accident. It also gives the client the opportunity to try a less severe response to resolving the error than terminating the thread.

Figure 2. Structure of Implementation A (basic)

Note: On most versions of Symbian OS, the tool chain disables the use of WSD in DLLs by default. To enable WSD you must add the EPOCALLOWDLLDATA keyword to your MMP file. However, there are some builds of Symbian OS (for example, Symbian OS v9.3 in S60 3rd Edition FP2) that allow WSD in DLLs without the need to use the EPOCALLOWDLLDATA keyword. In addition, the currently supported version of the GCC-E compiler has a defect such that DLLs with static data may cause a panic during loading. The issue, and how to work around it, is discussed further in [Willee, Nov 2007].

A variant of this approach is to give clients of the singleton more control and allow them to instantiate the singleton instance separately from actually using it (see Figure 3), allowing them to choose when to handle any possible errors that occur.

Figure 3. Structure of Implementation A (lifetime managed)

A separate method can then be supplied to guarantee access to the singleton instance once it has been instantiated.

class CSingleton : public CBase
{
public:
// To create the singleton instance
static void CreateL();
// To access the singleton instance
static CSingleton& Instance();
private: // The implementations of the following are omitted for clarity
static CSingleton* NewL();
CSingleton();
~CSingleton();
void ConstructL();
private:
static CSingleton* iSingletonInstance; // WSD
};

/*static*/ void CSingleton::CreateL()
{
// Flags up multiple calls in debug builds
ASSERT(!iSingletonInstance);

// Create the singleton if it doesn't already exist
if (!iSingletonInstance)
{
iSingletonInstance = CSingleton::NewL();
}
}

/*static*/ CSingleton& CSingleton::Instance()
{
ASSERT(iSingletonInstance); // Fail Fast (see page 17)
return (*iSingletonInstance);
}

This approach gives the caller more flexibility. Only one call to a method that can fail is necessary, in order to instantiate the singleton instance. Upon instantiation, the singleton is guaranteed to be returned as a reference, so removing the requirement for pointer verification or leave-safe code.

Positive 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 DLL using WSD.

  • Can only be used safely by one thread.

2.2. Implementation B: TLS Singleton in a Single Thread

Constraints 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 where WSD is not supported or recommended. This implementation can be used in DLLs on all versions of Symbian OS, and in EXEs if desired. It is not thread-safe.

Details

TLS is a single storage area per thread, one machine word in size (32 bits on Symbian OS v9).[] A pointer to the singleton object is saved to the TLS area and, whenever the data is required, the pointer in TLS is used to access it.

[] Note that, since TLS is provided on a per-thread basis, this implementation does not share the data between threads. See Implementations C, D or E for multi-threaded singletons.

The operations for accessing TLS are found in class Dll, which is defined 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 API of CSingleton, except that the methods to create and access the singleton, CreateL() and Instance(), must be exported in order to be accessible to client code outside the DLL, as must any specific methods that the singleton class provides. The implementation of CreateL() and Instance() must be modified as follows:

EXPORT_C /*static*/ void CSingleton::CreateL()
{
ASSERT(!Dll::Tls());
if (!Dll::Tls())
{ // No singleton instance exists yet so create one
CSingleton* 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 access[] through a RAM lookup. On ARMv6 CPU architectures, it is about 30 times slower however this should be considerably improved by subsequent CPU architectures.

    [] See [Sales, 2005].

  • Maintainability is reduced because of the need to manage the single TLS 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 as appropriate through the TLS pointer. This can be difficult to maintain.

2.3. Implementation C: Classic Thread-safe Singleton Within a Process

Constraints on the Singleton

  • Must be used within a single process but may be used across multiple threads.

  • May be used within an EXE on any version of Symbian OS or in a DLL on Symbian OS v9.x, if absolutely necessary.

  • WSD may be used.

Details

We earlier demonstrated that the Instance() method of the classical implementation of Singleton is not thread-safe – race conditions may occur 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 problem of making Singleton leave-safe – add a mutex. However, consider the following pseudocode:

/*static*/ Singleton& Singleton::Instance()
{
Mutex.Lock(); // Pseudocode
if (!pInstance_)
{
pInstance_ = new Singleton();
}
Mutex.Unlock(); // Pseudocode
return (*pInstance_);
}

The use of Mutex has prevented the potential for race conditions and ensures that only one thread executes the test for the existence of pInstance_ at any one time. However, it also means that the mutex must be locked and unlocked every time the singleton is accessed even though the singleton creation race condition we described earlier can only occur once. The acquisition of the mutex lock, and its release, results in an overhead that is normally unnecessary. The solution may have looked straightforward, but the overhead is far from desirable.

To work around this, perhaps we could only lock the mutex after the test for the existence of pInstance_? This gives us the following pseudocode:

/*static*/ Singleton& Singleton::Instance()
1 {
2 if (!pInstance_)
3 {
4 Mutex.Lock(); // Pseudocode
5 pInstance_ = new Singleton();
6 Mutex.Unlock(); // Pseudocode
7 }
8 return (*pInstance_);
9 }

However, this has reintroduced the possibility of a race-condition and thus a memory leak. Assume thread A runs and tests pInstance_ in line 2. If it is NULL, the code must lock the mutex and create the singleton. However, suppose thread A is interrupted, prior to executing line 4, by thread B. The singleton hasn't been created yet, so thread B passes through the if statement and on to line 4, locking the mutex and creating pInstance_, before unlocking the mutex in line 6. When thread A runs again, it is from line 4, and it proceeds to lock the mutex and create a second instance of the singleton. The check for the existence of pInstance_ comes too early if it occurs only before the lock.

This gives rise to an implementation that double checks the pInstance_ pointer: that is, checking it after acquiring the lock as well as before. 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 {
4 Mutex.Lock(); // Pseudocode
5 if (!pInstance_)
6 {
7 pInstance_ = new Singleton();
8 }
9 Mutex.Unlock(); // Pseudocode
10 }
11 return (*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 potential for a race condition is eliminated, because a check is also performed after the mutex is locked. It is these two checks that give rise to the name of DCLP.

Now let's consider the simplest DCLP implementation in Symbian C++, using WSD.[] As we saw in Implementation A, there are two possible approaches:

[] Please note that the implementation shown is unsafe, as described in the consequences sections. The DCLP should be used carefully until later versions of Symbian OS provide a memory barrier to guarantee the order of execution on both single- and multiple-processor hardware. Even then, there will still be the problem of using a globally-named mutex, which is insecure as we highlight in the consequences sections.

  • Provide a method to provide access to the singleton instance and create it if it does not already exist.

  • Split the creation of the singleton instance from the method required to access it, to make it easier for calling code to use the singleton without having to handle errors when memory resources are low.

The InstanceL() method for the former approach is as follows:[]

[] The two-phase construction code is as shown in Implementation A.

/*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 mutex

if (!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 shown below. Three threads, a main thread and two secondary threads, access the singleton, and call a method called DoSomething() to illustrate its use:

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 mutex
RMutex mutex;
User::LeaveIfError(mutex.CreateGlobal(KSingletonMutex));
CleanupClosePushL(mutex);
...
RThread thread1;
User::LeaveIfError(thread1.Create(_L("Thread1"), Thread1EntryPoint,
KDefaultStackSize, KMinHeapSize,
KMaxHeapSize, NULL));


CleanupClosePushL(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 mutex

if (!iSingletonInstance) // Second check
{
iSingletonInstance = CSingleton::NewL();
}

mutex.Signal(); // Unlock the mutex
}
}

The code that uses the singleton is now more straightforward, since the Instance() method always returns a valid instance and no errors have to be handled. However, to be sure that the singleton has been created, each thread must call the CSingleton::CreateL() method once, unless the thread is guaranteed to start after one or more other threads have already called CSingleton::CreateL().

Positive Consequences

  • Prevents race conditions and provides a leave-safe solution for use where WSD is available and multiple threads must be able to access a singleton.

  • Use of double-checked locking optimizes performance by locking the mutex on singleton creation only and not on every access.

Negative Consequences

  • 4 KB of RAM is used for the WSD in the one process sharing the singleton.

  • There is additional complexity of implementation and use compared to Implementation A. If it's possible to ensure access to the singleton by a single thread only, Implementation A is preferable.

  • Safe use of DCLP cannot currently be guaranteed. There is no way to prevent a compiler from reordering the code in such a way that the singleton pointer becomes globally visible before all the singleton is fully constructed. In effect, one thread could access a singleton that is only partially constructed. This is a particular hazard on multiprocessor systems featuring a 'relaxed' memory model to commit writes to main memory in bursts in order of address, rather than sequentially in the order they occur.[] Symmetric multiprocessing is likely to be found in future versions of Symbian OS. However, it is also a problem on single processor systems. The 'volatile' keyword can potentially be used to write a safe DCLP implementation but careful examination of the compiler documentation and a detailed understanding of the ISO C++ specification is required. Even then the resulting code is compiler-dependent and non-portable.

    [] [Alexandrescu, 2001] suggests that you should perform triple-checked locking by first checking your compiler documentation before going on to implement the DCLP! If you plan to use the DCLP, you should certainly consult [Myers and Alexandrescu, 2004] for a detailed discussion of the potential hazards. The final section of that paper suggests alternative ways to add thread-safety to Singleton that avoid the use of the DCLP.

  • The example given is insecure because it uses a globally accessible mutex. Global objects are inherently prone to security and robustness issues since a thread running in any process in the system can open them by name and, in this case, block legitimate threads attempting to create the singleton by calling Wait()Signal(). without ever calling

  • You cannot share a singleton across multiple threads executing in different processes. This is because each process has its own address space and a thread running in one process cannot access the address space of another process to get at the singleton object.

2.4. Implementation D: Thread-Managed Singleton within a Process

Constraints on the Singleton

  • Must be used within a single process but may be used across multiple threads.

  • May be used within an EXE or a DLL on any version of Symbian OS.

  • WSD may not be used.

Details

As its name suggests, the storage word used by TLS is local to a thread and each thread in a process has its own storage location. Where TLS is used to access a singleton in a multi-threaded environment, the pointer that references the location of the singleton must be passed to each thread. This is not a problem since all threads are in the same process and they can all access the memory address. This can be done using the appropriate parameter of RThread::Create(). If this is not done, when the new thread calls Dll::Tls(), it receives a NULL pointer.

This implementation does not need to use DCLP because, for the threads to share the singleton, its instantiation must be controlled and the location of an existing singleton passed to threads as they are created. The main thread creates the singleton before the other threads exist, thus the code is single-threaded at the point of instantiation and does not need to be thread-safe. Once the singleton is available, its location may be passed to other threads, which can then access it. Thus this implementation is described as 'thread managed' because the singleton cannot be lazily allocated, but must be managed by the parent thread.

Let's look at some code to clarify how this works. Firstly, the CSingleton class exports two additional methods that are used by calling code to retrieve the singleton instance pointer from the main thread, SingletonPtr(), and set it in the created thread, InitSingleton(). These methods are added so that code in a process that loads and uses the singleton-providing DLL can initialize and use the singleton without needing to be aware of how the singleton class is implemented.

class CSingleton : public CBase
{
public:
// To create the singleton instance
IMPORT_C static void CreateL();
// To access the singleton instance

IMPORT_C static CSingleton& Instance();
// To get the location of the singleton so that it can be passed to a
// new thread
IMPORT_C static TAny* SingletonPtr();
// To initialize access to the singleton in a new thread
IMPORT_C static TInt InitSingleton(TAny* aLocation);
private:
CSingleton();
~CSingleton();
void ConstructL();
};

EXPORT_C /*static*/ CSingleton& CSingleton::Instance()
{
CSingleton* singleton = static_cast<CSingleton*>(Dll::Tls());
return (*singleton);
}

EXPORT_C /*static*/ void CSingleton::CreateL()
{
ASSERT(!Dll::Tls());
if (!Dll::Tls()) // No singleton instance exists yet so create one
{
CSingleton* singleton = new(ELeave) CSingleton();
CleanupStack::PushL(singleton);
singleton->ConstructL();
User::LeaveIfError(Dll::SetTls(singleton));
CleanupStack::Pop(singleton);
}
}

EXPORT_C TAny* CSingleton::SingletonPtr()
{
return (Dll::Tls());
}

EXPORT_C TInt CSingleton::InitSingleton(TAny* aLocation)
{
return (Dll::SetTls(aLocation));
}


The code for the main thread of a process creates the singleton instance and, as an illustration, creates a secondary thread, in much the same way as we've seen in previous snippets:

// Main (parent) thread must create the singleton
CSingleton::CreateL();

//Create a secondary thread
RThread thread1;
User::LeaveIfError(thread1.Create(_L("Thread1"), Thread1EntryPoint,
KDefaultStackSize, KMinHeapSize,
KMaxHeapSize,
CSingleton::SingletonPtr()));
CleanupClosePushL(thread1);

// Resume thread1, etc...

Note that the thread creation function now takes the return value of CSingleton::SingletonPtr() as a parameter value (the value is the return value of Dll::Tls() – giving access to the TLS data of the main thread). The parameter value must then be passed to CSingleton::InitSingleton() in the secondary thread:

TInt Thread1EntryPoint(TAny* aParam)
{
TInt err = CSingleton::InitSingleton(aParam);
if (err == KErrNone )
{ // This thread can now access the singleton in the parent thread
...
}

return err;
}

Positive Consequences

  • Singleton can be implemented in a DLL and accessed by multiple threads in the process into which the DLL is loaded.

Negative Consequences

  • Accidental complexity of implementation and use. For example, code that executes in a secondary thread must initialize its own TLS data by explicitly acquiring and setting the location of the singleton in the parent thread.

  • The main thread must control the creation of every secondary thread.

  • This solution works for multiple threads executing in a single process, but cannot be used to share a singleton across multiple threads executing in different processes. This is because each process has its own address space and a thread running in one process cannot access the address space of another process.

2.5. Implementation E: System-wide Singleton

Constraints on the Singleton

  • Can be used anywhere in the system across multiple processes or threads.

  • May be used within an EXE or a DLL on any version of Symbian OS.

  • WSD may not be used.

Details

As described in the consequences sections of Implementations C and D, those implementations allow a singleton to be accessed by multiple threads within a single Symbian OS process, but not by multiple processes. The singleton implementation is only a single instance within a process. If several processes load a DLL which provides Implementation D, each process would have its own copy of a singleton to preserve an important quality of processes – that their memory is entirely isolated from any other process.

Where a system-wide singleton is required on Symbian OS, one process must be assigned ownership of the singleton object itself. Each time another process, or client, wishes to use the singleton it has to make an IPC request to get a copy of the singleton. To enable this, the provider of the singleton should make a client-side DLL available that exposes the singleton interface to these other processes but is implemented to use IPC transparently to obtain the copy of the actual singleton.

Having hidden the IPC details from clients behind what is effectively a Proxy [Gamma et al., 1994] based on the singleton interface, you have a choice of which IPC mechanism to use to transmit the copy of the singleton data. Here are some mechanisms provided by Symbian OS that you can use to perform thread-safe IPC without the need for WSD or the DCLP:

  • Client–Server (see page 182)

    This has been the most commonly used mechanism to implement this pattern as it has existed in Symbian OS for many versions now. The server owns the singleton and sends copies on request to its clients.

  • Publish and Subscribe kernel service

    You may find that if all you wish to do is provide a system-wide singleton then using the Publish and Subscribe kernel service is easier to implement. Note that whilst this is the same technology as used in Publish and Subscribe (see page 114), the way it is used differs. For instance, whilst the owning process does indeed 'publish' the singleton, the client-side DLL does not 'subscribe' for notification of changes. Instead, it simply calls RProperty::Get() when an instance of the singleton is needed.

  • Message Queues

    Mentioned for completeness here as they could be used but the other alternatives are either more familiar or simpler to use.

The disadvantage of having a system-wide singleton is the impact on run-time performance. The main overhead arises from the context switch involved in the IPC message to and from the process owning the singleton and, if the singleton contains a lot of data, the copying of the singleton.

Positive Consequences

  • Allows access by multiple processes and thus provides a single, system-wide, instance.

  • The Symbian OS IPC mechanisms manage the multiple requests for access to a single shared resource and handle most of the problems of making the singleton thread-safe.

  • Calling code can be unaware of whether it is accessing a singleton – the client-side implementation is simple to use, while the process supplying the singleton can be customized as necessary.

Negative Consequences

  • Additional execution time caused by the IPC and copying of the singleton data although any implementation of Singleton that supports access from multiple processes will also be affected by this.

  • The implementation of the singleton is necessarily more complex, requiring creation of a client-side DLL and use of an IPC mechanism. The implementation can be heavyweight if only a few properties or operations are offered by the singleton.

2.6. Solution Summary

Figure 4 summarizes the decision tree for selecting a singleton implementation on Symbian OS.

2.7. Example Resolved

The control environment (CONE) implements class CCoeEnv as a singleton class. Implementation B is used and a pointer to the singleton is stored in TLS. Internally, CCoeEnv accesses the singleton instance through a set of inline functions, as follows:

// coetls.h
class CCoeEnv;

inline CCoeEnv* TheCoe() { return((CCoeEnv*)Dll::Tls()); }
inline TInt SetTheCoe(CCoeEnv* aCoe) { return (Dll::SetTls(aCoe)); }
inline void FreeTheCoe() { Dll::FreeTls(); }

The constructor of CCoeEnv uses an assertion statement to confirm that the TLS slot returned by TheCoe() is uninitialized. It then sets the TLS word to point to itself, by calling SetTheCoe(this).

When an object of any class, instantiated within a thread that has a control environment (i.e. the main thread of an application) needs access to the CCoeEnv singleton, it may simply include the coemain.h header and call CCoeEnv::Static() which is implemented as follows in coemain.cpp:

Figure 4. Symbian OS Singleton implementation decision tree

EXPORT_C CCoeEnv* CCoeEnv::Static()
{
return(TheCoe());
}

It can then be used by applications as follows:

CCoeEnv::Static()->CreateResourceReaderLC(aReader, aResourceId);

Alternatively, CCoeControl::ControlEnv() can be used by applications to retrieve the singleton instance. However, this method returns a cached pointer to the CCoeEnv singleton, which the CCoeControl class stores in its constructor (by making a call to TheCoe()).

The use of a cached pointer is more efficient than calling CCoeEnv:: Static() because it avoids making an executive call to Dll::Tls() to access the singleton. Within the application framework, using a cached pointer is acceptable, because the lifetime of the CCoeEnv singleton is controlled and, if the application is running, the CCoeEnv object is guaranteed to exist (and to be at the same location as when the CCoeControl constructor first cached it).

In general, it is not considered a good thing to cache pointers to singleton objects for future access, because it makes the design inflexible. For example, the singleton can never be deleted without providing some kind of mechanism to ensure that those using a cached pointer don't access dead memory. The Instance() method should generally be used to access the singleton, because that allows more flexibility to the provider of the singleton. For example, it could save on memory by deleting the object if available memory drops below a certain threshold or if it has not been accessed recently or regularly.

The implementations given in the Solution section anticipate that some clients may mistakenly cache a pointer to the singleton 'because they can' and instead return a reference from Instance(), which is more difficult to code around (although it is still possible).

3. Other Known Uses

This pattern is widely used across Symbian OS since virtually every application makes use of the CCoeEnv singleton in its thread whilst every server is itself a singleton for the whole system.

4. Variants and Extensions

Destroying the Singleton

All the implementations discussed have specified the destructor for the singleton class as private. This is because, if the destructor was public, it is possible that the singleton instance could be deleted, leaving the singleton class to hand out 'dangling references' to the deleted instance. Unless the design is such that this can be guaranteed not to occur, it is preferable to prevent it.

Since the singleton class has responsibility for creation of the instance, it must generally have responsibility of ownership and, ultimately, cleanup.

One option could be to allow the singleton destruction to be implicit. C++ deletes static objects automatically at program termination and the language guarantees that an object's destructor will be called and space reclaimed at that time. It doesn't guarantee the calling order, but if there is only one singleton in the system, or the order of destruction is unimportant, this is an option when Implementations A or C (using WSD) are chosen. Please see [Vlissides, 1996] for more information about this approach, which requires the use of a friend class to destroy the singleton.

Another option is to use the atexit function provided by the standard C library, to register a cleanup function to be called explicitly when the process terminates. The cleanup function can be a member of the singleton class and simply delete the singleton instance. Please see [Alexandrescu, 2001] for further details.

However, you may want to destroy the singleton before the process terminates (for example, to free up memory if the object is no longer needed). In this case, you have to consider a mechanism such as reference counting to avoid dangling references arising from premature deletion. For example, to extend Implementation A, with the addition of a static Close() method:

class CSingleton : public CBase
{
public:
// To create the singleton instance
static void CreateL();
// To access the singleton instance
static CSingleton& Instance();
static void Close();
private: // The implementations of the following are omitted for
clarity
CSingleton* NewL();
CSingleton();
~CSingleton();
void ConstructL();
private:
static CSingleton* iSingletonInstance;
TInt iRefCount;
};

/*static*/ void CSingleton::CreateL()
{
++(iSingletonInstance->iRefCount);
if (!iSingletonInstance)
{ // Create the singleton if it doesn't already exist
iSingletonInstance = CSingleton::NewL();
}
}

/*static*/ CSingleton& CSingleton::Instance()
{
ASSERT(iSingletonInstance);
return (*iSingletonInstance);
}


// Must be called once for each call to CreateL()
/*static*/ void CSingleton::Close()
{
if (--(iSingletonInstance->iRefCount)<=0)
{
delete iSingletonInstance;
iSingletonInstance = NULL;
}
}
 
Others
 
- Mapping Well-Known Patterns onto Symbian OS : Model–View–Controller
- The Anatomy of a Mobile Site : STYLING WITH CSS - CSS Considerations for Mobile & Optimizing CSS
- The Anatomy of a Mobile Site : INVOKING OTHER DEVICE CAPABILITIES & THE STATE OF JAVASCRIPT
- iPad Development : The Dual-Action Color Popover (part 3) - Serving Two Masters
- iPad Development : The Dual-Action Color Popover (part 2) - Hooking Up the Grid
- iPad Development : The Dual-Action Color Popover (part 1) - Creating a Simple Color Grid
- XNA Game Studio 3.0 : Creating Fake 3-D - Creating Shadows Using Transparent Colors
- Android Application Development : ViewGroups (part 2) - ListView, ListActivity, ScrollView & TabHost
- Android Application Development : ViewGroups (part 1) - Gallery and GridView
- Java ME on Symbian OS : MIDP 2.0 Game API Core Concepts
 
 
Top 10
 
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
Technology FAQ
- Is possible to just to use a wireless router to extend wireless access to wireless access points?
- Ruby - Insert Struct to MySql
- how to find my Symantec pcAnywhere serial number
- About direct X / Open GL issue
- How to determine eclipse version?
- What SAN cert Exchange 2010 for UM, OA?
- How do I populate a SQL Express table from Excel file?
- code for express check out with Paypal.
- Problem with Templated User Control
- ShellExecute SW_HIDE
programming4us programming4us