C++11 concurrency and multithreading note taking singleton design pattern share data analysis, solution, call_once

Posted by ksandom on Sat, 06 Nov 2021 23:00:11 +0100

1. About design patterns

Design pattern

  • Some writing methods of the code (these writing methods are different from the conventional writing methods), so that the program written by the code is flexible and easy to maintain, but others take over and read the code is very painful.
  • The code written with the concept of "design pattern" is very obscure;
  • When dealing with a particularly large project, summarize and sort out the development experience and module division experience of the project into a design mode (development needs first, followed by theoretical summary and sorting).
  • Design pattern must have its unique advantages. We should learn and use it flexibly, and don't get trapped and copy mechanically.

2. Singleton design pattern

  • Single case design mode, with high frequency;
  • In the whole project, there is a certain or some special class. Only one object belonging to this class can be created, but more can not be created.

Example code:

#include <iostream>
using namespace std;

class MyCAS
{
private:
	MyCAS() {}	//Privatized constructor

private:
	static MyCAS* m_instance;	//Static member variable

public:
	static MyCAS* GetInstance()
	{
		if (m_instance == NULL)
		{
			m_instance = new MyCAS();
			static CGarhuishou cl;
		}
		return m_instance;
	}

	class CGarhuishou	//Class, which is used to release objects
	{
	public:
		~CGarhuishou()
		{
			if (MyCAS::m_instance)
			{
				delete MyCAS::m_instance;
				MyCAS::m_instance = NULL;
			}
		}
	};

	void func()
	{
		cout << "test" << endl;
	}
};

//Class static variable initialization
MyCAS* MyCAS::m_instance = NULL;

int main()
{
	MyCAS* p_a = MyCAS::GetInstance();		//Create an object and return a pointer to the class (MyCAS) object
	MyCAS* p_b = MyCAS::GetInstance();
	p_a->func();
	MyCAS::GetInstance()->func();

	return 0;
}

3. Analysis and solution of sharing data in single case design pattern

  • Problem: we need to create the object of the singleton class MyCAS in our own thread (not the main thread). There may be more than one thread (at least two).
  • We may face that member functions such as GetInstance() are mutually exclusive.
  • Double locking is adopted to improve efficiency, so that it is not necessary to add a lock and unlock it every time you enter the GetInstance() function.

Example code:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::mutex resource_mutex;
class MyCAS
{
private:
	MyCAS() {}	//Privatized constructor

private:
	static MyCAS* m_instance;	//Static member variable

public:
	static MyCAS* GetInstance()
	{
		if (m_instance == NULL)		//Double locking (double check)
		{
			std::unique_lock<std::mutex> mymutex(resource_mutex);
			if (m_instance == NULL)
			{
				m_instance = new MyCAS();
				static CGarhuishou cl;
			}
		}
		return m_instance;
	}

	class CGarhuishou	//Class, which is used to release objects
	{
	public:
		~CGarhuishou()
		{
			if (MyCAS::m_instance)
			{
				delete MyCAS::m_instance;
				MyCAS::m_instance = NULL;
			}
		}
	};

	void func()
	{
		cout << "test" << endl;
	}
};

//Class static variable initialization
MyCAS* MyCAS::m_instance = NULL;

void mythread()
{
	cout << "My thread started executing" << endl;
	MyCAS* p_a = MyCAS::GetInstance();
	cout << "My thread is finished" << endl;
}

int main()
{
	std::thread mytobj1(mythread);
	std::thread mytobj2(mythread);
	/*Although the two threads are the same entry function,
	But remember, these are two threads,
	Therefore, two processes (two paths) will start to execute the mythread function at the same time
	*/
	mytobj1.join();
	mytobj2.join();

	return 0;
}

4,std::call_once()

std::call_once()

  • The second parameter of the function introduced by c++11 is a function name a().
  • call_ The once () function ensures that function a() is called only once.
  • call_once() has the ability of mutex and consumes less resources than mutex in efficiency;
  • call_once() needs to be used in conjunction with a tag, which is std::once_flagļ¼Œonce_ Flag is a structure.
  • call_once() uses this tag to determine whether the corresponding function a() is executed and calls call_ After once() succeeds, call_once() sets this flag to the called state. Call again later_ Once(), as long as once_ If the flag is set to the "called" state, the corresponding function a() will no longer be executed.

Example code:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::mutex resource_mutex;
std::once_flag g_flag;	//System defined tags

class MyCAS
{
	static void CreatInstance()	//Called only once
	{
		std::chrono::milliseconds dura(20000);		//Rest for 20s
		std::this_thread::sleep_for(dura);
		cout << "CreatInstance()Executed" << endl;

		m_instance = new MyCAS();
		static CGarhuishou cl;
	}

private:
	MyCAS() {}	//Privatized constructor

private:
	static MyCAS* m_instance;	//Static member variable

public:
	static MyCAS* GetInstance()
	{
		std::call_once(g_flag, CreatInstance);	//Two threads execute here at the same time. One thread has to wait for the other thread to finish executing. CreatInstance()
		cout << "call_once()completion of enforcement" << endl;
		
		return m_instance;
	}

	class CGarhuishou	//Class, which is used to release objects
	{
	public:
		~CGarhuishou()
		{
			if (MyCAS::m_instance)
			{
				delete MyCAS::m_instance;
				MyCAS::m_instance = NULL;
			}
		}
	};

	void func()
	{
		cout << "test" << endl;
	}
};

//Class static variable initialization
MyCAS* MyCAS::m_instance = NULL;

void mythread()
{
	cout << "My thread started executing" << endl;
	MyCAS* p_a = MyCAS::GetInstance();
	p_a->func();
	cout << "My thread is finished" << endl;
	return;
}

int main()
{
	std::thread mytobj1(mythread);
	std::thread mytobj2(mythread);
	/*Although the two threads are the same entry function,
	But remember, these are two threads,
	Therefore, two processes (two paths) will start to execute the mythread function at the same time
	*/
	mytobj1.join();
	mytobj2.join();

	return 0;
}
  • It is recommended to create a singleton object in the main function (main thread) first, and then create a child thread.
  • std::call_once is actually less efficient than double locking. Double locking is recommended.

Note: I study c + + multithreading video address: C + + multithreaded learning address

Topics: C++ Multithreading