Singleton Pattern of C++ Programming

content

1. Thread Safety

2. Example of thread unsafety

2.1 Example 1:

2.2 Example 2:

3. The difference between hungry mode and lazy mode


The so-called singleton pattern means that the class has one and only one object in memory.

1. Thread Safety

The so-called thread safety refers to: when multiple threads are created in our entire program, and these multiple threads may run the same piece of code, if the result of each multi-threaded program running this code is the same as the single-threaded program running this The result of the code is the same, and the variable values ​​are the same as expected, then the multithreaded program is thread-safe.

Example:

The main function creates two threads, makes the two threads call the static method in the Object class, and prints the object address returned by the static method

#include <iostream>
#include <thread>
using namespace std;
//1、线程安全的情况
class Object
{
private:
	int value;
	static Object instance;//静态变量需要类外初始化
	static int num;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object& GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		return instance;
	}
};
Object Object::instance(10);
void funa()
{
	Object& obja = Object::GetInstance();//调用静态方法返回静态对象本身
	cout << &obja << endl;
}
void funb()
{
	Object& objb = Object::GetInstance();//调用静态方法返回静态对象本身
	cout << &objb << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

Program running result:

After the above code is executed, the two printed object addresses are the same.

explain:

The static object instance has been constructed in the data area before entering the main function, and its value=10 is set, and then after entering the main function, whether it is a thra thread or a thrb thread, it can only access this static object instance, and No changes will be made to it, so it is thread-safe.

2. Example of thread unsafety

Explain two cases of thread insecurity through the following examples, and explain what is hungry mode and what is lazy mode.

2.1 Example 1:

Before explaining the hungry man mode, let's first understand how static variables are initialized during the running of the program, which is convenient for understanding the subsequent examples.

It is different for a program to initialize static variables with literal constants and variables at the low level.

Example:

void funa(int x)
{
    static int a=10;
}
int main()
{
     funa(10);
}

Static constant: When static int a=10 is encountered before entering the main function, the variable a will be placed in the data area, and a space of a certain size will be opened up, and then an initial value of 10 will be assigned (this is thread-safe).

Example:

void funb(int x)
{
    static int b=x;
}
int main()
{
    funb(10);
}

Static variable: encounter static int b=x before entering the main function; first allocate memory to this variable b in the data area. Since you don't know how much to assign, so don't assign it first. At this time, there will be a flag fig inside the static variable =0 means no assignment. Call funb(10) after the main function, and then go to the data area for assignment. At this time, the fig flag changes from 0 to 1, indicating that the initial value has been assigned. Therefore, when there is a variable, the data area is to modify the flag fig first, and then modify the value (this is thread-unsafe, because if the program has multiple threads competing to change the flag bit fig of the variable in the data area).

After working through an understanding of how programs handle static variables, take a look at the following example:

The main function creates two threads, which respectively call the static methods in the Object class, pass parameters to x, and then construct a static object and return the object.

//饿汉模式
class Object
{
public:
	int value;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object& GetInstance(int x)//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		//直接在静态函数中构建这个静态对象并返回其本身
		static Object instance(x);
		return instance;
	}
};
void funa()
{
	Object& obja = Object::GetInstance(10);//调用静态方法返回静态对象本身
	cout << obja.value << endl;
	cout << &obja << endl;
}
void funb()
{
	Object& objb = Object::GetInstance(20);//调用静态方法返回静态对象本身
	cout << objb.value << endl;
	cout << &objb << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

Program running result:

 First run:

Second run:

It can be seen that the above example is not thread-safe. We originally expected that the thra thread would pass the parameter 10 to x, build a static object with value=10, and then return the static object. The thrb thread passes a parameter of 20 to x, then constructs a static object with value=20, and returns the static object. But the result of the program running is that there is only one object, and the value of value is undefined, not the result we expected.

reason:

static Object instance(x); This sentence will allocate address space in the data area before entering the main function, start the thread after entering the main function, and the two threads will run the static method in the Object class at the same time (there is only one static method share), there is a problem of resource competition, competing for the right to mark fig from 0 to 1, which thread grabs the opportunity, it will assign its own value to value, then the thread that did not grab it will find fig when it visits again. The flag has been set to 1, indicating that it has been assigned, so it can only exit the static method.

Finally, let’s talk about what the Hungry Man mode is. From the above example of building a static object, we can see that the Hungry Man mode means that as long as the class we design starts to load, the singleton must be initialized to ensure that multiple threads access this unique resource. , the singleton already exists (take the above example: once the Object class starts to load, the static Object instance(x) object needs to be initialized quickly, so as to ensure that multiple threads call this unique static method when , the static object already exists and no other changes are required by the thread).

2.2 Example 2:

Example:

The main function starts two threads, and the two threads respectively call the static method in the Object class. When the static pointer pobj is empty, an Object object is constructed from the heap area, and then the static opposite pointer pobj is returned. Point to the Object object constructed in the heap area, and then the two threads respectively print the address of the object pointed to by the pointer.

class Object
{
public:
	int value;
	static Object* pobj;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object* GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		if (pobj == NULL)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
			pobj = new Object(10);
		}
		return pobj;
	}
};
Object* Object::pobj = NULL;
void funa()
{
	Object* pa = Object::GetInstance();
	cout << pa << endl;
}
void funb()
{
	Object* pb = Object::GetInstance();
	cout << pb  << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

Program running result:

First run:

Second run:

It can be seen from the program running results that the object addresses returned by the two threads are not the same, but we expect that when multiple threads execute the same program under thread safety, there should be only one object returned and the address is unique. The reason why this is happening now is:

Both threads entered the static method GetInstance(), and both found that the pobj pointer was empty, so they both entered the If statement, then I set a sleep time, and then one executed pobj = new Object(10); Created an object for pobj, and return to print the address of the object, and then another thread executes pobj = new Object(10);; creates another object and re-assigns pobj, and then prints the address of the object.

So it can be seen from the above example: the so-called lazy mode means that when the class we design starts to load, we do not need to initialize the singleton quickly, but wait until it is used for the first time (so it is said to be lazy). For the lazy mode, if you want to ensure thread safety and only create one object, you can lock the part of the code that creates the object.

The modified code is as follows:
 

#include <mutex>
std::mutex mtx;
class Object
{
public:
	int value;
	static Object* pobj;
private:
	Object(int x = 0) :value(x) {}//构造函数设为私有,外部函数无法访问
	Object(const Object& obj) = delete;//删除拷贝构造
	Object& operator=(const Object& ob) = delete;//删除等号运算符重载
public:
	static Object* GetInstance()//因为删除了拷贝构造,所以只能以引用或者指针的形式返回这个静态对象本身
	{
		std::lock_guard<std::mutex>lock(mtx);
		if (pobj == NULL)
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(10));
			pobj = new Object(10);
		}
		return pobj;
	}
};
Object* Object::pobj = NULL;
void funa()
{
	Object* pa = Object::GetInstance();
	cout << pa << endl;
}
void funb()
{
	Object* pb = Object::GetInstance();
	cout << pb  << endl;
}
int main()
{
	thread thra(funa);
	thread thrb(funb);

	thra.join();
	thrb.join();

	return 0;
}

3. The difference between hungry mode and lazy mode

(1) Hungry man mode:

The object has been created when the class is loaded, and the program will directly obtain the previously constructed instance object when it is called.

In this way, since the constructed object is static, it will always occupy the space of the data area, but it saves the time of constructing the object when calling.

(2) Lazy mode:

Objects are constructed only when accessing static methods.

This method is only constructed when it needs to be used, which saves space to a certain extent and prevents objects from wasting space in memory all the time, but when they are used, they will consume a certain amount of time and make the program slower.

This mode is not safe and requires a lock for control.

Guess you like

Origin blog.csdn.net/m0_54355780/article/details/123188535