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

Lock Guard

We can use the lock guard to wrap the mutex and the guard will unlock the mutex when it's destructor is called ( goes out of scope ) .

File: guard1.cpp
#include <iostream>
#include <thread>
#include <mutex>

using namespace std ;

mutex mtx ;

void thread_function1()
{
   for( int i1=0 ; i1<5 ; i1++ )
     {
       lock_guard lk( mtx ) ;
       cout << "Thread function1: " << i1 << endl ;

     }

}

void thread_function2()
{
   for( int i1=0 ; i1<5 ; i1++ )
    {
      lock_guard 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;
}
Output:

$ rm a.exe ; g++ guard1.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

We wrap the mutex with a guard in the statement:

lock_guard lk( mtx ) ;

This causes a lock to be placed on the mutex and after
printing out the statement the lokc guard object goes
out of scope causing the "unlock" method of the mutex
to be called.
The "lock_guard" can only be used with 1 mutex and that means that it cannot be copied. The following program illustrates this.

File: guard2.cpp
#include <iostream>
#include <mutex>
#include <thread>

using namespace std ;

mutex myMutex ;

void protectedFunction()
{
    lock_guard<mutex> lock(myMutex) ; // Locks myMutex
    cout << "Inside protected function." << endl;
    lock_guard<mutex> lock1 = lock  ;
    // ... critical section code ...
} // myMutex is automatically unlocked when 'lock' goes out of scope

void anotherFunction( lock_guard<mutex> lockGuardObject )
{

}

int main()
{
    lock_guard<mutex> lock1(myMutex); // Locks myMutex
    cout << "Lock1 acquired." << endl;
    anotherFunction( lock1 ) ;
    // The following line would result in a compilation error:
    // lock_guard<mutex> lock2 = lock1; // ERROR: Cannot copy lock_guard

    // You also cannot pass a lock_guard by value to a function:
    // void anotherFunction(lock_guard<mutex> l) {}
    // anotherFunction(lock1); // ERROR: Cannot copy lock_guard

    thread t1(protectedFunction);
    t1.join();

    cout << "Lock1 released." << endl; // lock1 goes out of scope here
    return 0;
}
The lock guard is useful when exceptions are thrown. We know that the local objects are destroyed when the function throws an exception and that ensures that the "unlock" method is called.

File: guard3.cpp
#include <iostream>
#include <mutex>
#include <stdexcept> // For runtime_error

using namespace std ;

mutex global_mutex; // A global mutex to protect shared resources

void protected_function()
{
    cout << "Attempting to acquire lock..." << endl;
    // lock_guard acquires the lock in its constructor
    // and automatically releases it in its destructor when it goes out of scope.
    lock_guard<mutex> lock(global_mutex);
    cout << "Lock acquired." << endl;

    // Simulate some work that might throw an exception
    bool should_throw = true; // Set to true to demonstrate exception handling
    if (should_throw) {
        cout << "Throwing an exception..." << endl;
        throw runtime_error("Simulated error in critical section!");
    }

    cout << "Work completed (this line won't be reached if exception is thrown)." << endl;
    // The lock_guard's destructor will be called here if no exception,
    // releasing the mutex.
} // lock_guard goes out of scope here, releasing the mutex (even on exception)

int main()
{
    try
    {
        protected_function();
    }
    catch (const runtime_error& e)
    {
        cerr << "Caught exception in main: " << e.what() << endl;
    }

    // Attempt to acquire the lock again to demonstrate it was released
    cout << "Attempting to acquire lock in main after protected_function..." << endl;
    lock_guard<mutex> lock_in_main(global_mutex);
    cout << "Lock successfully acquired in main, demonstrating previous release." << endl;

    return 0;
}

Exercise

1) Use a guard instead in the below code.

File: guard_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 lock guard 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;
}
2) Explain the problem in the following code.

File: guard_ex_2.cpp
#include <iostream>
#include <mutex>
#include <thread>
#include <stdexcept> // For runtime_error

using namespace std ;

mutex global_mutex ; // A global mutex to protect shared resources

void protected_function()
{
    cout << "Attempting to acquire lock..." << endl;
    // lock_guard acquires the lock in its constructor
    // and automatically releases it in its destructor when it goes out of scope.
    lock_guard<mutex> lock(global_mutex);
    cout << "Lock acquired." << endl;
    // Simulate some work that might throw an exception

    cout << "Work completed (this line won't be reached if exception is thrown)." << endl;
} // lock_guard goes out of scope here, releasing the mutex (even on exception)

int main()
{
        lock_guard<mutex> lock(global_mutex)  ;
        thread t1( protected_function );
        thread t2( protected_function );

        t1.join() ;
        t2.join() ;



    return 0;
}
















































Solutions

1)

File: guard_ex_1s.cpp
2)

File: guard_ex_2s.cpp