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