Home C++ Decisions Loops Input/Output Functions Stack and Heap References Arrays Searching and Sorting Recursion Pointers Character and Strings Structures Classes Inheritance Exceptions Templatess STL Modern C++ Misc Books ----

Threads


Contents

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" parts

File: 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