What is thread safety?

In the parallel execution of programs with multiple threads with shared data, thread-safe code ensures that each thread can execute normally and correctly through the synchronization mechanism without data contamination and other unexpected situations.


How to keep thread safe?

  1. Lock shared resources to ensure that each resource variable is occupied by no more than one thread at any time.
  2. Let threads own resources instead of sharing resources in the process. For example, with ThreadLocal you can maintain a private local variable for each thread.

What is the singleton pattern?

The singleton pattern ensures that only one instance of a class can be generated during the entire system life cycle to ensure the uniqueness of the class.

Singleton pattern classification

Singletons can be divided into slacker and hungry-hunk patterns, and the difference between the two is when the instance is created:

  • Lazy: An instance does not exist when the system is running. An instance is created and used only when needed. (Consider thread safety in this approach)
  • Hungry: initializes and creates instances as soon as the system runs, and invokes them directly when needed. (thread safety itself, no multithreading problem)

Singleton class characteristics

  • Constructors and destructors are of private type and are intended to disallow external construction and destructor
  • The copy constructor and assignment constructor is of type private to prevent external copy and assignment and ensure instance uniqueness
  • The class has a static function that gets the instance, accessible globally

01 Common Lazy singleton (Thread unsafe)

/ / / / / / / / / / / / / / / / / / / ordinary LanHanShi implementation - thread safe / / / / / / / / / / / / / / / / / /
#include <iostream> // std::cout
#include 
       
         // std::mutex
       
#include <pthread.h> // pthread_create

class SingleInstance
{

public:
    // Get the singleton
    static SingleInstance *GetInstance(a);

    // Release the singleton, called when the process exits
    static void deleteInstance(a);
	
	// Prints the singleton address
    void Print(a);

private:
	// Make its construction and destruct private, disallow external construction and destruct
    SingleInstance();
    ~SingleInstance();

    // Make its copy constructor and assignment constructor private, disallow external copy and assignment
    SingleInstance(const SingleInstance &signal);
    const SingleInstance &operator= (const SingleInstance &signal);

private:
    // unique singleton pointer
    static SingleInstance *m_SingleInstance;
};

// Initialize static member variables
SingleInstance *SingleInstance::m_SingleInstance = NULL;

SingleInstance* SingleInstance::GetInstance()
{

	if (m_SingleInstance == NULL)
	{
	    m_SingleInstance = new (std::nothrow) SingleInstance;  // No locking is thread unsafe. Multiple instances can be created when threads are concurrent
	}

    return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = NULL; }}void SingleInstance::Print()
{
	std: :cout << "My instance memory address is :" << this << std: :endl;
}

SingleInstance::SingleInstance()
{
    std: :cout << "Constructor" << std: :endl;
}

SingleInstance::~SingleInstance()
{
    std: :cout << "Destructor" << std: :endl;
}
/ / / / / / / / / / / / / / / / / / / ordinary LanHanShi implementation - thread safe / / / / / / / / / / / / / / / / / /

// Thread function
void *PrintHello(void *threadid)
{
    // The main thread is separated from the child thread, and the child thread is automatically reclaimed when the child thread terminates
    pthread_detach(pthread_self());

    // Cast the passed argument from an untyped pointer to an integer pointer, and then read
    int tid = *((int *)threadid);

    std: :cout << "Hi, I'm thread ID:[" << tid << "]" << std: :endl;

    // Prints the instance address
    SingleInstance::GetInstance()->Print();

    pthread_exit(NULL);
}

#define NUM_THREADS 5 // Number of threads

int main(void)
{
    pthread_t threads[NUM_THREADS] = {0};
    int indexes[NUM_THREADS] = {0}; // Use an array to hold the value of I

    int ret = 0;
    int i = 0;

    std: :cout << "Main () : Start... " << std: :endl;

    for (i = 0; i < NUM_THREADS; i++)
    {
        std: :cout << "Main () : create thread :[" << i << "]" << std: :endl;
        
        indexes[i] = i; // Save the value of I
		
        // When passed, it must be cast to void*, i.e., no type pointer
        ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));
        if (ret)
        {
            std: :cout << "Error: could not create thread," << ret << std: :endl;
            exit(- 1); }}// Manually release the resources of a single instance
    SingleInstance::deleteInstance();
    std: :cout << "Main () : End! " << std: :endl;
	
    return 0;
}
Copy the code

Results of ordinary lazy singleton:

The singleton constructor creates two memory addresses 0x7F3C980008c0 and 0x7F3C900008c0, so the ordinary lazy singleton is only suitable for a single process, not for multithreading, because it is not thread safe.


02 Locked lazy singleton (thread-safe)

/////////////////// locked lazy implementation //////////////////
class SingleInstance
{

public:
    // Get the singleton
    static SingleInstance *&GetInstance(a);

    // Release singleton, called when the process exits
    static void deleteInstance(a);
	
    // Prints the instance address
    void Print(a);

private:
    // Make its construction and destruct private, disallow external construction and destruct
    SingleInstance();
    ~SingleInstance();

    // Make its copy constructor and assignment constructor private, disallow external copy and assignment
    SingleInstance(const SingleInstance &signal);
    const SingleInstance &operator= (const SingleInstance &signal);

private:
    // Unique singleton pointer
    static SingleInstance *m_SingleInstance;
    static std::mutex m_Mutex;
};

// Initialize static member variables
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;

SingleInstance *&SingleInstance::GetInstance()
{

    // The technique of using two if statements is called double-checking; The nice thing is that the lock is only done if the pointer is null,
    // Avoid locking every time you call GetInstance. Locking is a bit expensive.
    if (m_SingleInstance == NULL) 
    {
        std::unique_lock<std::mutex> lock(m_Mutex); / / lock
        if (m_SingleInstance == NULL)
        {
            m_SingleInstance = new (std::nothrow) SingleInstance; }}return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    std::unique_lock<std::mutex> lock(m_Mutex); / / lock
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = NULL; }}void SingleInstance::Print()
{
	std: :cout << "My instance memory address is :" << this << std: :endl;
}

SingleInstance::SingleInstance()
{
    std: :cout << "Constructor" << std: :endl;
}

SingleInstance::~SingleInstance()
{
    std: :cout << "Destructor" << std: :endl;
}
/////////////////// locked lazy implementation //////////////////
Copy the code

The result of the locked lazy singleton:

As you can see from the results, only one instance is created and the memory address is 0x7F28B00008C0, so plain lazy with mutex is thread-safe


03 lazy singletons for internal static variables (C++11 thread-safe)

/ / / / / / / / / / / / / / / / / / / internal static variable idlers / / / / / / / / / / / / / / / / / /
class Single
{

public:
    // Get the singleton
    static Single &GetInstance(a);
	
	// Prints the instance address
    void Print(a);

private:
    // Disallow external constructs
    Single();

    // Disallow external destructions
    ~Single();

    // Disable external copy constructs
    Single(const Single &signal);

    // Disallow external assignment operations
    const Single &operator= (const Single &signal);
};

Single &Single::GetInstance()
{
    // Implement singleton in a local static way
    static Single signal;
    return signal;
}

void Single::Print()
{
    std: :cout << "My instance memory address is :" << this << std: :endl;
}

Single::Single()
{
    std: :cout << "Constructor" << std: :endl;
}

Single::~Single()
{
    std: :cout << "Destructor" << std: :endl;
}
/ / / / / / / / / / / / / / / / / / / internal static variable idlers / / / / / / / / / / / / / / / / / /
Copy the code

Results of lazy singletons with internal static variables:

-std=c++0x compilation uses the c++ 11 feature, which is thread-safe in c++ 11 internal static variables. Only one instance is created, and the memory address is 0x6016e8. This method is recommended and requires minimal code.

[root@lincoding singleInstall]#g++ SingleInstance.cpp -o SingleInstance -lpthread -std=c++0x
Copy the code


04 Hungry singleton (inherently thread-safe)

/ / / / / / / / / / / / / / / / / / / / / / / / / / hungry / / / / / / / / / / / / / / / / / / / / /
class Singleton
{
public:
    // Get a single instance
    static Singleton* GetInstance(a);

    // Release singleton, called when the process exits
    static void deleteInstance(a);
    
    // Prints the instance address
    void Print(a);

private:
    // Make its construction and destruct private, disallow external construction and destruct
    Singleton();
    ~Singleton();

    // Make its copy constructor and assignment constructor private, disallow external copy and assignment
    Singleton(const Singleton &signal);
    const Singleton &operator= (const Singleton &signal);

private:
    // Unique singleton pointer
    static Singleton *g_pSingleton;
};

// Initializing the creation instance as soon as the code runs is inherently thread-safe
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;

Singleton* Singleton::GetInstance()
{
    return g_pSingleton;
}

void Singleton::deleteInstance()
{
    if (g_pSingleton)
    {
        delete g_pSingleton;
        g_pSingleton = NULL; }}void Singleton::Print()
{
    std: :cout << "My instance memory address is :" << this << std: :endl;
}

Singleton::Singleton()
{
    std: :cout << "Constructor" << std: :endl;
}

Singleton::~Singleton()
{
    std: :cout << "Destructor" << std: :endl;
}
/ / / / / / / / / / / / / / / / / / / / / / / / / / hungry / / / / / / / / / / / / / / / / / / / / /
Copy the code

Results of hanhanian singleton:

From the results of the run, we know that hanhanian is initialized at the beginning of the program constructor, so it is inherently thread-safe


Features and Selection

  • Slacker is time for space, adapt to the traffic is small; Lazy singletons with internal static variables are recommended with less code
  • Hungry style is space for time, suitable for large traffic, or more threads