Linux C++ thread-safe singleton mode summary

What is thread safety?
In a program that has multiple threads executing in parallel with shared data, thread-safe code will ensure that each thread can execute normally and correctly through a synchronization mechanism, and there will be no unexpected situations such as data pollution.

How to ensure thread safety?
Add locks to shared resources to ensure that each resource variable is occupied by at most one thread at all times.
Let threads also own resources, without sharing resources in the process. For example: Use threadlocal to maintain a private local variable for each thread.
What is the singleton pattern?
Singleton mode refers to ensuring that only one instance of a class can be generated in the entire system life cycle to ensure the uniqueness of the class.

Classification of
singleton mode Singleton mode can be divided into lazy man style and hungry man style. The difference between the two lies in the time to create an instance:

Lazy man: refers to the instance does not exist in the system is running, only when the instance needs to be used, will create and use the instance. (Thread safety should be considered in this way)
Hungry Chinese style: refers to the initialization and creation of an instance as soon as the system is running, and it can be called directly when needed. (It is thread-safe in itself, and there is no problem of multithreading)
Singleton features: The
constructor and destructor are of private type, and the purpose is to prohibit external construction and destruction.
Copy construction and assignment The constructor is of private type, and the purpose is to prohibit external copy and assignment. , To ensure the uniqueness of the instance
. There is a static function to get the instance in the class, which can be accessed globally.
01 Ordinary lazy singleton (thread unsafe)
/// Ordinary lazy implementation – thread unsafe //
#include // std:: cout
#include // std::mutex
#include <pthread.h> // pthread_create

class SingleInstance
{

public:
// Get a single instance
static SingleInstance *GetInstance();

// 释放单例,进程退出时调用
static void deleteInstance();

// 打印单例地址
void Print();

private:
// Make its construction and analysis private, and prohibit external construction and destruction
SingleInstance();
~SingleInstance();

// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);

private:
// The only single instance object pointer
static SingleInstance *m_SingleInstance;
};

//Initialize the static member variable
SingleInstance *SingleInstance::m_SingleInstance = NULL;

SingleInstance* SingleInstance::GetInstance()
{

if (m_SingleInstance == NULL)
{
    m_SingleInstance = new (std::nothrow) SingleInstance;  // 没有加锁是线程不安全的,当线程并发时会创建多个实例
}

return m_SingleInstance;

}

void SingleInstance::deleteInstance()
{
if (m_SingleInstance)
{
delete m_SingleInstance;
m_SingleInstance = NULL;
}
}

void SingleInstance::Print()
{ std::cout << "The memory address of my instance is:" << this << std::endl; }

SingleInstance::SingleInstance()
{
std::cout << “构造函数” << std::endl;
}

SingleInstance::~SingleInstance()
{ std::cout << "destructor" << std::endl; } /// Ordinary lazy implementation-thread unsafe //


// Thread function
void *PrintHello(void *threadid)
{ // The main thread and the child thread are separated, and the two do not interfere with each other. The child thread ends and the resources of the child thread are automatically recovered pthread_detach(pthread_self());

// 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
int tid = *((int *)threadid);

std::cout << "Hi, 我是线程 ID:[" << tid << "]" << std::endl;

// 打印实例地址
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 store the value of i

int ret = 0;
int i = 0;

std::cout << "main() : 开始 ... " << std::endl;

for (i = 0; i < NUM_THREADS; i++)
{
    std::cout << "main() : 创建线程:[" << i << "]" << std::endl;
    
    indexes[i] = i; //先保存i的值
    
    // 传入的时候必须强制转换为void* 类型,即无类型指针
    ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));
    if (ret)
    {
        std::cout << "Error:无法创建线程," << ret << std::endl;
        exit(-1);
    }
}

// 手动释放单实例的资源
SingleInstance::deleteInstance();
std::cout << "main() : 结束! " << std::endl;

return 0;

}
Ordinary lazy singleton running result: Insert picture description here
From the running result, we can see that two singleton constructors are created, the memory addresses are 0x7f3c980008c0 and 0x7f3c900008c0, so the ordinary lazy singleton is only suitable for single process and not suitable for multithreading, because it is not suitable for multithreading. safe.

02 Locked lazy singleton (thread safe)
/// Locked lazy implementation //
class SingleInstance
{

public:
// Get a single instance object
static SingleInstance *&GetInstance();

//释放单实例,进程退出时调用
static void deleteInstance();

// 打印实例地址
void Print();

private:
// Make its construction and analysis private, and prohibit external construction and destruction
SingleInstance();
~SingleInstance();

// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
SingleInstance(const SingleInstance &signal);
const SingleInstance &operator=(const SingleInstance &signal);

private:
// A pointer to a single instance object
static SingleInstance *m_SingleInstance;
static std::mutex m_Mutex;
};

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

SingleInstance *&SingleInstance::GetInstance()
{

//  这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
//  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
if (m_SingleInstance == NULL) 
{
    std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
    if (m_SingleInstance == NULL)
    {
        m_SingleInstance = new (std::nothrow) SingleInstance;
    }
}

return m_SingleInstance;

}

void SingleInstance::deleteInstance()
{
std::unique_lockstd::mutex lock(m_Mutex); // 加锁
if (m_SingleInstance)
{
delete m_SingleInstance;
m_SingleInstance = NULL;
}
}

void SingleInstance::Print()
{ std::cout << "The memory address of my instance is:" << this << std::endl; }

SingleInstance::SingleInstance()
{
std::cout << “构造函数” << std::endl;
}

SingleInstance::~SingleInstance()
{ std::cout << "destructor" << std::endl; } /// The locked lazy implementation // The locked lazy singleton operation result: from The result of the operation shows that only one instance is created, and the memory address is 0x7f28b00008c0, so the ordinary lazy style with mutex lock is thread-safe



Insert picture description here

03 The lazy singleton of internal static variables (C++11 thread safety)
/// The lazy implementation of internal static variables //
class Single
{

public:
// Get a single instance object
static Single &GetInstance();

// 打印实例地址
void Print();

private:
// prohibit external construction
Single();

// 禁止外部析构
~Single();

// 禁止外部复制构造
Single(const Single &signal);

// 禁止外部赋值操作
const Single &operator=(const Single &signal);

};

Single &Single::GetInstance()
{ // Realize single instance with local static characteristics static Single signal; return signal; }



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

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

Single::~Single()
{ std::cout << "destructor" << std::endl; } /// The lazy implementation of internal static variables // The running result of lazy singleton of internal static variables: - The std=c++0x compilation uses the characteristics of C++11. The method of internal static variables in C++11 is thread-safe. The instance is created only once, and the memory address is 0x6016e8. This method is very recommended. The code is the least!




[root@lincoding singleInstall]#g++ SingleInstance.cpp -o SingleInstance -lpthread -std=c++0x
Insert picture description here

04 Hungry man-style singleton (itself thread safe)
// Hungry man implementation/
class Singleton
{ public: // Get a single instance static Singleton* GetInstance();


// 释放单实例,进程退出时调用
static void deleteInstance();

// 打印实例地址
void Print();

private:
// Make its construction and analysis private, and prohibit external construction and destruction
Singleton();
~Singleton();

// 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
Singleton(const Singleton &signal);
const Singleton &operator=(const Singleton &signal);

private:
// The only singleton object pointer
static Singleton *g_pSingleton;
};

// Initialize and create an instance as soon as the code runs, which is 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 << "The memory address of my instance is:" << this << std::endl; }

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

Singleton::~Singleton()
{ std::cout << "destructor" << std::endl; } // The running result of the hungry man realization/ hungry man-style singleton: From the running result, the hungry man-style The constructor is initialized at the beginning of the program, so it is thread-safe in itself



Insert picture description here

Features and choices The
lazy man style uses time to exchange space, which is suitable for small visits; it is recommended to use the lazy man singleton with internal static variables, the amount of code is small, the
hungry man style uses space for time, which is suitable for large visits or threads More cases

Need C/C++ Linux server architect learning materials plus skirt 563998835 (data including C/C++, Linux, golang technology, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP/IP, coroutine, DPDK, ffmpeg, etc.), free sharing

Guess you like

Origin blog.csdn.net/qq_40989769/article/details/106098901