C++11 concurrency and multithreading unique_lock details

Posted by naturalbeauty7 on Sat, 06 Nov 2021 08:16:43 +0100

1,unique_lock replaces lock_guard

unique_lock

  • Is a class template. In work, lock is generally used_ Guard (recommended); lock_guard replaces lock() and unlock() of mutex;
  • unique_lock is better than lock_ The guard is much more flexible; The efficiency is a little poor, and the memory takes up a little more.
  • In normal use, there is no difference when the parameter has only one mutex.

2,unique_lock second parameter

2.1 std::adopt_lock

std::adopt_lock

  • std::adopt_lock: indicates that the mutex has been locked (the mutex must be locked in advance before use, otherwise an exception will be reported).
  • std::adopt_ The effect of the lock tag is to "assume that the caller thread has the ownership of mutual exclusion (that is, lock() has succeeded).
  • Notify unique_lock that the mutex lock() in the constructor is not needed.

Example code:

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

class A
{
public:
	//The thread that puts the received message (player command) into a queue
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			cout << "inMsgRecvQueue()Execute, insert an element:" << i << endl;

			my_mutex.lock();		//Lock first, and then use the std::adopt_lock parameter of unique_lock
			std::unique_lock<std::mutex> sbguard1(my_mutex,std::adopt_lock);

			msgRecvQueue.push_back(i);

		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		if (!msgRecvQueue.empty())
		{
			//Message is not empty
			command = msgRecvQueue.front();		//Returns the first element without checking whether the element exists
			msgRecvQueue.pop_front();							//Removes the first element, but does not return
			return true;
		}

		return false;
	}

	//A thread that fetches data from a message queue
	void outMsgRecvQueue()
	{
		int command = 0;

		for (int i = 0; i < 10000; i++)
		{
			bool result = outMsgLULProc(command);

			if (result == true)
			{
				cout << "outMsgRecvQueue()Execute and take out an element" << command << endl;
				//Next, consider processing data
			}
			else
			{
				//Message queue is empty
				cout << "outMsgRecvQueue()Executed, but the message queue is currently empty" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//Container, specially used to represent the commands sent by players to us
	mutex my_mutex;					//Created a mutex

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutnMsgObj.join();


	cout << "I live China!" << endl;	//Finally, execute this sentence and the whole process exits

	system("pause");
	return 0;
}

2.2 std::try_to_lock

std::try_to_lock()

  • Try to lock the mutex with mutex's lock(). If the lock is not successful, it will return immediately and will not block there.
  • The premise of using this try_to_lock is that you can't go to lock first.

Example code:

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

class A
{
public:
	//The thread that puts the received message (player command) into a queue
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			cout << "inMsgRecvQueue()Execute, insert an element:" << i << endl;

			std::unique_lock<std::mutex> sbguard1(my_mutex,std::try_to_lock);
			if (sbguard1.owns_lock())
			{
				//Got the lock
				msgRecvQueue.push_back(i);
				//......
			}
			else
			{
				cout << "inMsgRecvQueue()But I didn't get the lock. I had to do something else" << i << endl;
			}
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		std::chrono::milliseconds dura(20000);		//1s = 1000ms,20000ms = 20s
		std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//Message is not empty
			command = msgRecvQueue.front();		//Returns the first element without checking whether the element exists
			msgRecvQueue.pop_front();							//Removes the first element, but does not return
			return true;
		}

		return false;
	}

	//A thread that fetches data from a message queue
	void outMsgRecvQueue()
	{
		int command = 0;

		for (int i = 0; i < 10000; i++)
		{
			bool result = outMsgLULProc(command);

			if (result == true)
			{
				cout << "outMsgRecvQueue()Execute and take out an element" << command << endl;
				//Next, consider processing data
			}
			else
			{
				//Message queue is empty
				cout << "outMsgRecvQueue()Executed, but the message queue is currently empty" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//Container, specially used to represent the commands sent by players to us
	mutex my_mutex;					//Created a mutex

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutnMsgObj.join();


	cout << "I live China!" << endl;	//Finally, execute this sentence and the whole process exits

	system("pause");
	return 0;
}

2.3 std::defer_lock

std::defer_lock

  • The premise of using std::defer_lock is that you can't lock() first, otherwise an exception will be reported.
  • defer_lock means that a mutex without a lock is initialized without a lock.

3. Member function of unique_lock

3.1 lock(),unlock()

Example code:

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

class A
{
public:
	//The thread that puts the received message (player command) into a queue
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex, std::defer_lock);	//my_mutex without lock
			sbguard1.lock();		//Don't unlock yourself

			//Because there is some unshared code to process, you need to unlock()
			sbguard1.unlock();
			//Here we deal with some unshared code

			//After processing the unshared code, continue locking
			sbguard1.lock();
			//Shared code is handled here

			//Got the lock
			msgRecvQueue.push_back(i);
			//......

			sbguard1.unlock();		//Paint the snake, but it's OK
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		//std::chrono::milliseconds dura(20000);		//1s = 1000ms,20000ms = 20s
		//std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//Message is not empty
			command = msgRecvQueue.front();		//Returns the first element without checking whether the element exists
			msgRecvQueue.pop_front();							//Removes the first element, but does not return
			return true;
		}

		return false;
	}

	//A thread that fetches data from a message queue
	void outMsgRecvQueue()
	{
		int command = 0;

		for (int i = 0; i < 10000; i++)
		{
			bool result = outMsgLULProc(command);

			if (result == true)
			{
				cout << "outMsgRecvQueue()Execute and take out an element" << command << endl;
				//Next, consider processing data
			}
			else
			{
				//Message queue is empty
				cout << "outMsgRecvQueue()Executed, but the message queue is currently empty" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//Container, specially used to represent the commands sent by players to us
	mutex my_mutex;					//Created a mutex

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutnMsgObj.join();


	cout << "I live China!" << endl;	//Finally, execute this sentence and the whole process exits

	system("pause");
	return 0;
}

3.2 try_lock())

try_lock()

  • Try to lock the mutex. If you can't get the lock, return false. If you get the lock, return true. This function is not blocked.

Example code:

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

class A
{
public:
	//The thread that puts the received message (player command) into a queue
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex, std::defer_lock);	//my_mutex without lock

			if (sbguard1.try_lock() == true)		//Returning true means you have got the lock
			{
				msgRecvQueue.push_back(i);
				cout << "inMsgRecvQueue()Execute, insert an element " << i << endl;
				//......
			}
			else
			{
				cout << "inMsgRecvQueue()But I didn't get the lock. I had to do something else" << i << endl;
			}
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//Message is not empty
			command = msgRecvQueue.front();		//Returns the first element without checking whether the element exists
			msgRecvQueue.pop_front();							//Removes the first element, but does not return
			return true;
		}

		return false;
	}

	//A thread that fetches data from a message queue
	void outMsgRecvQueue()
	{
		int command = 0;

		for (int i = 0; i < 10000; i++)
		{
			bool result = outMsgLULProc(command);

			if (result == true)
			{
				cout << "outMsgRecvQueue()Execute and take out an element" << command << endl;
				//Next, consider processing data
			}
			else
			{
				//Message queue is empty
				cout << "outMsgRecvQueue()Executed, but the message queue is currently empty" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//Container, specially used to represent the commands sent by players to us
	mutex my_mutex;					//Created a mutex

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutnMsgObj.join();


	cout << "I live China!" << endl;	//Finally, execute this sentence and the whole process exits

	system("pause");
	return 0;
}

3.3 release()

release()

  • Return the mutex object pointer it manages and release the ownership; that is, the unique_lock is no longer related to mutex.
  • Strictly distinguish the difference between unlock() and release(). Don't confuse them.
  • If the original mutex object is locked, the programmer has the responsibility to take over and unlock it.

Example code:

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

class A
{
public:
	//The thread that puts the received message (player command) into a queue
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex);	//my_mutex without lock
			std::mutex* ptx = sbguard1.release();	//Now it's your responsibility to unlock this my_mutex yourself

			msgRecvQueue.push_back(i);
			cout << "inMsgRecvQueue()Execute, insert an element " << i << endl;

			ptx->unlock();		//Be responsible for the unlock() of mutex
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//Message is not empty
			command = msgRecvQueue.front();		//Returns the first element without checking whether the element exists
			msgRecvQueue.pop_front();		    //Removes the first element, but does not return
			return true;
		}

		return false;
	}

	//A thread that fetches data from a message queue
	void outMsgRecvQueue()
	{
		int command = 0;

		for (int i = 0; i < 10000; i++)
		{
			bool result = outMsgLULProc(command);

			if (result == true)
			{
				cout << "outMsgRecvQueue()Execute and take out an element" << command << endl;
				//Next, consider processing data
			}
			else
			{
				//Message queue is empty
				cout << "outMsgRecvQueue()Executed, but the message queue is currently empty" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//Container, specially used to represent the commands sent by players to us
	mutex my_mutex;					//Created a mutex

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutnMsgObj.join();


	cout << "I live China!" << endl;	//Finally, execute this sentence and the whole process exits

	system("pause");
	return 0;
}

Why do you need unlock() sometimes?

  • Because the fewer code segments locked by lock(), the faster the execution, and the higher the efficiency of the whole program.
  • The number of code locked by the lock head is called lock granularity, which is generally described by thickness.
  • There is less locked code, which is called fine granularity and high execution efficiency.
  • If there are many locked codes, the granularity is called coarse, and the execution efficiency is low.
  • We should learn to select code with appropriate granularity for protection as much as possible. If the strength is too fine, the protection of shared data may be missed. If the granularity is too coarse, it will affect the efficiency.
  • Choosing the appropriate granularity is the embodiment of the ability and strength of senior programmers.

4. unique_lock ownership transfer

  • std::unique_lock<std::mutex> sbguard1(my_mutex)
  • sbguard1 owns my_mutex
  • sbguard1 can transfer its ownership of mutex(my_mutex) to other unique_lock objects.
  • The ownership of unique_lock object to mutex can be transferred, but cannot be copied.

4.1 std::move

Example code of the first std::move ownership transfer:

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

class A
{
public:
	//The thread that puts the received message (player command) into a queue
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard1(my_mutex);	//my_mutex without lock

			std::unique_lock<std::mutex> sbguard2(std::move(sbguard1));

			msgRecvQueue.push_back(i);
			cout << "inMsgRecvQueue()Execute, insert an element " << i << endl;
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		//std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		//std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//Message is not empty
			command = msgRecvQueue.front();		//Returns the first element without checking whether the element exists
			msgRecvQueue.pop_front();							//Removes the first element, but does not return
			return true;
		}

		return false;
	}

	//A thread that fetches data from a message queue
	void outMsgRecvQueue()
	{
		int command = 0;

		for (int i = 0; i < 10000; i++)
		{
			bool result = outMsgLULProc(command);

			if (result == true)
			{
				cout << "outMsgRecvQueue()Execute and take out an element" << command << endl;
				//Next, consider processing data
			}
			else
			{
				//Message queue is empty
				cout << "outMsgRecvQueue()Executed, but the message queue is currently empty" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//Container, specially used to represent the commands sent by players to us
	mutex my_mutex;					//Created a mutex

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutnMsgObj.join();


	cout << "I live China!" << endl;	//Finally, execute this sentence and the whole process exits

	system("pause");
	return 0;
}

4.2 return std::unique_lockstd::mutex

The second return STD:: unique_ Lock < STD:: mutex > example code of ownership transfer:

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

class A
{
public:
	std::unique_lock<std::mutex> rtn_unique_lock()
	{
		std::unique_lock<std::mutex> tmpguard(my_mutex);
		return tmpguard;		//Returns a local unique from the function_ The lock object is OK
										//Return this local object tmp_guard will cause the system to generate temporary unique_lock object and call unique_ Move constructor for lock
	}

	//The thread that puts the received message (player command) into a queue
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> tmpguard1 = rtn_unique_lock();

			msgRecvQueue.push_back(i);
			cout << "inMsgRecvQueue()Execute, insert an element " << i << endl;
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{

		std::unique_lock<std::mutex> sbguard1(my_mutex);

		//std::chrono::milliseconds dura(500);		//1s = 1000ms,500ms = 0.5s
		//std::this_thread::sleep_for(dura);

		if (!msgRecvQueue.empty())
		{
			//Message is not empty
			command = msgRecvQueue.front();		//Returns the first element without checking whether the element exists
			msgRecvQueue.pop_front();							//Removes the first element, but does not return
			return true;
		}

		return false;
	}

	//A thread that fetches data from a message queue
	void outMsgRecvQueue()
	{
		int command = 0;

		for (int i = 0; i < 10000; i++)
		{
			bool result = outMsgLULProc(command);

			if (result == true)
			{
				cout << "outMsgRecvQueue()Execute and take out an element" << command << endl;
				//Next, consider processing data
			}
			else
			{
				//Message queue is empty
				cout << "outMsgRecvQueue()Executed, but the message queue is currently empty" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue;		//Container, specially used to represent the commands sent by players to us
	mutex my_mutex;					//Created a mutex

};

int main()
{
	A myobja;
	thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);

	myInMsgObj.join();
	myOutnMsgObj.join();


	cout << "I live China!" << endl;	//Finally, execute this sentence and the whole process exits

	system("pause");
	return 0;
}

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

Topics: C++ Multithreading