Threads
Unique Lock
A Unique Lock is also a wrapper for mutexes. We will rewrite the "guard1.cpp" to use "unique_lock" instead of the guard.File: unique1.cpp
#include <iostream> #include <thread> #include <mutex> using namespace std ; mutex mtx ; void thread_function1() { for( int i1=0 ; i1<5 ; i1++ ) { unique_lock lk( mtx ) ; cout << "Thread function1: " << i1 << endl ; } } void thread_function2() { for( int i1=0 ; i1<5 ; i1++ ) { unique_lock lk( mtx ) ; cout << "Thread function2: " << i1 << endl ; } } int main() { thread t1( &thread_function1 ); // t1 starts running thread t2( &thread_function2 ); // t2 starts running //cout << "main thread\n"; // main thread waits for the thread t1 to finish t1.join(); t2.join(); return 0; } $ rm a.exe ; g++ unique1.cpp ; ./a.exe Thread function1: 0 Thread function2: 0 Thread function1: 1 Thread function2: 1 Thread function1: 2 Thread function2: 2 Thread function1: 3 Thread function2: 3 Thread function1: 4 Thread function2: 4 The code is almost the same as the one written for "guard1.cpp". So what does "unique_lock" have to offer that "lock_guard" does not have. The unique lock is more flexible to use as it has functions to lock, unlock, recursive locking and can be used with condition variables. We study condition variables in the next section.
File: unique2.cpp
#include <iostream> #include <thread> #include <mutex> using namespace std ; mutex global_mutex; // A global mutex to protect shared data int shared_data = 0; void deferred_lock() { cout << "Thread " << this_thread::get_id() << " is starting." << endl; // Create a unique_lock, but defer locking the mutex // The mutex will NOT be locked upon construction of 'lock' unique_lock<mutex> lock(global_mutex, defer_lock); // Perform some operations that do not require the mutex cout << "Thread " << this_thread::get_id() << " doing non-critical work..." << endl; this_thread::sleep_for(chrono::milliseconds(100)); // Simulate some work // Now, explicitly acquire the lock when the critical section is needed cout << "Thread " << this_thread::get_id() << " attempting to lock mutex..." << endl; lock.lock(); // This will block if the mutex is already locked by another thread // Critical section: safely modify shared_data shared_data++; cout << "Thread " << this_thread::get_id() << " modified shared_data to: " << shared_data << endl; // Optionally, release the lock early if the critical section is over // and more non-critical work needs to be done within the same scope lock.unlock(); cout << "Thread " << this_thread::get_id() << " unlocked mutex and doing more non-critical work." << endl; this_thread::sleep_for(chrono::milliseconds(50)); // If the lock is needed again, it can be re-acquired lock.lock(); shared_data++; cout << "Thread " << this_thread::get_id() << " re-locked mutex and modified shared_data to: " << shared_data << endl; // The mutex will be automatically unlocked when 'lock' goes out of scope // (if it's currently held) cout << "Thread " << this_thread::get_id() << " is finishing." << endl; } int main() { thread t1(deferred_lock); thread t2(deferred_lock); t1.join(); t2.join(); cout << "Final shared_data value: " << shared_data << endl; return 0; }We also have the concept of "recursive_mutex" that is like a mutex but allows the thread to acquire the lock multiple times.
File: unique3.cpp
#include <mutex> #include <thread> #include <iostream> using namespace std ; recursive_mutex recursiveMutex ; int shared_resource = 0; void recursiveFunction(int depth) { if (depth <= 0) { return; } recursiveMutex.lock(); // Acquire the lock cout << "Thread " << this_thread::get_id() << " acquired lock at depth " << depth << endl; shared_resource++; // Access shared resource recursiveFunction(depth - 1); // Recursive call, re-acquiring the lock cout << "Thread " << this_thread::get_id() << " releasing lock at depth " << depth << endl; recursiveMutex.unlock(); // Release the lock } int main() { thread t1(recursiveFunction, 2); thread t2(recursiveFunction, 2); t1.join(); t2.join(); cout << "Final shared_resource value: " << shared_resource << endl; return 0; }We can also wrap the recursive mutex in a unique lock.
File: unique4.cpp
#include <iostream> #include <mutex> #include <thread> using namespace std ; recursive_mutex myRecursiveMutex; void recursiveFunction(int depth) { unique_lock<recursive_mutex> lock(myRecursiveMutex); // Acquires the lock cout << "Thread " << this_thread::get_id() << " entered recursiveFunction at depth " << depth << endl; if (depth > 0) { recursiveFunction(depth - 1); // Recursive call, re-acquires the lock } cout << "Thread " << this_thread::get_id() << " exiting recursiveFunction at depth " << depth << endl; } // Lock is released here (when unique_lock goes out of scope) int main() { thread t1(recursiveFunction, 2); t1.join(); return 0; }
Exercise
1) Complete the "TO DO" partsFile: unique_ex_1.cpp
#include <iostream> #include <thread> #include <mutex> using namespace std ; mutex gmutex ; void thread_function1( int id ) { for( int i1=1 ; i1<=5 ; i1++ ) { //TO DO Use a unique lock instead gmutex.lock() ; cout << "Thread id: " << id << " i1:" << i1 << endl ; gmutex.unlock() ; } } int main() { //TO DO Create 3 threads that all call "thread_function1" //at the same time. Pass the id's of 1 , 2 and 3 to the //function thread t1( &thread_function1, 1 ) ; thread t2( &thread_function1, 2 ) ; thread t3( &thread_function1, 3 ) ; //Have the 3 threads join in the main t1.join() ; t2.join() ; t3.join() ; cout << "main thread\n" ; return 0; }1) The following problem gives the right results and calls the 2 threads but has a problem. Uuncomment the "sleep_for" to see the issue and fix it.
File: unique_ex_2.cpp
#include <iostream> #include <mutex> #include <thread> using namespace std ; recursive_mutex myRecursiveMutex; void function2( int thread_id ) { unique_lock<recursive_mutex> lock(myRecursiveMutex); cout << "function2: with thread id of:" << thread_id << endl ; } void function1( int thread_id ) { cout << "function1: with thread id of:" << thread_id << endl ; myRecursiveMutex.lock(); for( int i1=1; i1<=5 ; i1++ ) { function2( thread_id ) ; } } int main() { thread t1( function1, 1 ); //this_thread::sleep_for(chrono::milliseconds(10)); thread t2( function2, 2 ); t1.join(); t2.join(); return 0; }
Solutions
1)File: unique_ex_1s.cpp
2)
File: unique_ex_2s.cpp