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?
- Lock shared resources to ensure that each resource variable is occupied by no more than one thread at any time.
- 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