Mutex(mutual exclusion): It provides the concurrent access to resource. In other word, It prevent other thread from locking a mutex until first thread unlock the mutex. At a same time only one thread access the resource and other thread will wait for it until acquired thread unlock the mutex.
In mutex, we have ownership that mean only acquired thread only unlock the resource. There are 2 main API:
std::mutex m; m.lock(); //do something m.unlock();
In c++ 11, We will not use it (mutex) directly (I mean we can use but we have better option of RAII principal (If exception happens after acquiring the mutex then it will be released in destruction otherwise it would have locked forever)), it will be used through two classes:
- std::lock_guard
- std::unique_lock
Recursive lock:
As it name tell us , we need to lock the mutex recursively.
When mutex is part of class function, this function is calling other member function (this function is using the same mutex).
If its normal mutex then it will block during second call, solution of this problem is that we need to use recursive mutex.
Recursive lock will be done by same thread and release of lock will be done when last unlock is called.
Tried lock:
We want to acquire a lock but we do not want block the thread and we can do some thing else.
while (m.try_lock() == false) { //sleep //do some thing } //execution come here that means we got the mutex and its locked //adopt_lock mean mutex is already locked so we are adapting it std::lock_guard<std::mutex> lg(m, std::adopt_lock);
Multiple lock:
Some time we need to use 2 mutex like m1 and m2. There order is very important, This can lead to deadlock. so we can solve this problem using std::lock() function.
std::lock(m1, m2); //lock is blocking call until all mutex are locked. //we need to use adapt_lock flag. //adopt_lock mean mutex is already locked so we are adapting it std::lock_guard<std::mutex>lg(m1, std::adopt_lock);
Difference between unique lock and lock guard (unique lock vs lock guard) :
std:lock_guard
It will get the mutex in constructor and release the mutex after completion of scope(block) in destructor. It will have mutex through out his life, it will NOT release in between. It has only three API:
- lock_guard obj1(m) //create object to lock the mutex.
- lock_guard obj2(m, adopt_lock) //create object to lock the already locked mutex.
- ~lock_guard() //release the mutex.
std::unique_lock
It is the superior version of lock guard, It has same API of lock_guard and additional API like lock, unlock and try lock etc. so that we can control the mutex from externally. It may or may have mutex when object is created.
Advantage of unique lock over lock guard:
- more flexible
- It has all API of lock guard plus additional API so that we can explicitly lock and unlock the mutex.
- It may or may not have mutex locked but lock guard has always locked the mutex throughout the life.
- In unique lock, User can query, it has mutex or not.
- We can release the mutex any time using obj.unlock(), if we want but it is not possible in lock guard.
- We can transfer the mutex we want to do so.
- We can defer the lock so that we can lock the mutex later
NOTE:
std::adopt_lock: we will use this flag when mutex is already locked and we want to initialize the lock guard or unique lock object.
std::defer_lock: we will use this flag when want to initialize the unique lock object WITHOUT locking the mutex, mutex will be locked later on.
Unique lock API:
- unique_lock obj1(m) //create object to lock the mutex as like above
- unique_lock obj2(m, adopt_lock) //create object to lock the already locked mutex
- ~unique_lock() //release the mutex
- obj1.lock() //Additional API to lock the mutex, it blocking call
- obj1.unlock() //Additional API to unlock the mutex
- try_lock() //Additional API to lock the mutex and return immediately with either true or false
- try_lock_for() //Additional API to lock the mutex and return after given duration with either true or false.
Note: Shared_mutex in c++14
Source code of lock guard with example:
/* Program: Mutex with Lock Guard Author: Alpha Master Date: 7 Feb 2021 */ //Header File #include<iostream> #include<thread> #include<chrono> #include<mutex> //Global Variable std::mutex gMtx; //Print Thread void PrintThread(std::string str) { { std::lock_guard<std::mutex> lg(gMtx); //purposefully waiting to verify the result std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout<<"Value: "; for(auto i : str) { std::cout<<i; } std::cout<<std::endl; } } int main() { std::cout<<"Mutex with Lock Guard"<<std::endl; //Thread 1 and its joinable std::thread t1(PrintThread, "please print this"); //Thread 2 and its also joinable std::thread t2(PrintThread, "This should be printed"); //std::cout<<"Main thread is Waiting for Child thread"<<std::endl; t1.join(); t2.join(); return 0; };
Output WITHOUT mutex:
Mutex with Lock Guard Value: Value: Tphliease psr isnt thihsould b e printed
Output with mutex:
Mutex with Lock Guard Value: please print this Value: This should be printed
Source code of unique lock with example:
/* Program: Mutex with Unique Lock Author: Alpha Master Date: 7 Feb 2021 */ //Header File #include<iostream> #include<thread> #include<chrono> #include<mutex> //Global Variable std::mutex gMtx; //Print Thread void PrintThread(std::string str) { { std::unique_lock<std::mutex> lg(gMtx); //purposefully waiting to verify the result std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout<<"Value: "; for(auto i : str) { std::cout<<i; } std::cout<<std::endl; if(lg) { lg.unlock(); std::cout<<"Mutex status: "<<lg.owns_lock()<<std::endl; //lock the mutex again, will release in destructor lg.lock(); std::cout<<"Mutex status: "<<lg.owns_lock()<<std::endl; } } } int main() { std::cout<<"Mutex with Lock Guard"<<std::endl; //Thread 1 and its joinable std::thread t1(PrintThread, "please print this"); //Thread 2 and its also joinable std::thread t2(PrintThread, "This should be printed"); //std::cout<<"Main thread is Waiting for Child thread"<<std::endl; t1.join(); t2.join(); return 0; };
Output:
Mutex with Lock Guard Value: This should be printed Mutex status: 0 Mutex status: 1 Value: please print this Mutex status: 0 Mutex status: 1