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

Mutex

We have the concept of a "mutex" . The mutex object has 2 methods "lock()" and "unlock()" . Suppose we have a single mutex object and 2 threads hit the "lock" method at the same time; then only 1 thread will enter the code after the lock. The other thread will be forced to wait till the first thread releases the lock with the "unlock()" call.
File: thread4.cpp
#include <iostream>
#include <thread>
#include <mutex>

using namespace std ;
//Using a mutex
mutex mtx ;

void thread_function1()
{
   mtx.lock();
   for( int i1=0 ; i1<5 ; i1++ )
    cout << "Thread function1: " << i1 << endl ;
   mtx.unlock();
}

void thread_function2()
{
   mtx.lock();
   for( int i1=0 ; i1<5 ; i1++ )
    cout << "Thread function2: " << i1 << endl ;
   mtx.unlock();
}


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++ thread4.cpp ; ./a.exe
Thread function1: 0
Thread function1: 1
Thread function1: 2
Thread function1: 3
Thread function1: 4
Thread function2: 0
Thread function2: 1
Thread function2: 2
Thread function2: 3
Thread function2: 4

The output looks much cleaner. So what's going on over here ?

thread t1( &thread_function1 );   // t1 starts running

The above statement starts to execute the function "thread_function1".
The first statement in this function is:

 mtx.lock();

The "mtx" is a global object and "t1" locks the mutex and starts to
execute rest of the instructions in the function.

Meanwhile t2 also starts to execute right after t1 started. So t2 will
enter "thread_function2()" and execute:

 mtx.lock();

Since the mutex is locked by "thread_function1()" the t2 thread
waits until "thread_function1()" finishes and releases the lock.
What if we forget to release the lock in ""thread_function1()" ?
Then "thread_function2()" keeps waiting ( forever ) for the lock
to be available. We shall see later how "lock_guard" can resolve
the problem of forgetting to call "unlock()".

In the program we are running "thread_function1()" and then
running ""thread_function2()" . We can also use the mutex to
make sure that only 1 thread runs their "cout" at a time by
placing the mutex just before the "cout" .

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

using namespace std ;

mutex mtx ;

void thread_function1()
{
   for( int i1=0 ; i1<5 ; i1++ )
     {
       mtx.lock() ;
       cout << "Thread function1: " << i1 << endl ;
       mtx.unlock() ;
     }

}

void thread_function2()
{
   for( int i1=0 ; i1<5 ; i1++ )
    {
      mtx.lock() ;
      cout << "Thread  function2: " << i1 << endl ;
      mtx.unlock() ;
    }

}


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++ thread5.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 can tell from the output that the behavior has changed from
one thread getting control of the mutex to the threads alternating
in printing out their statement. It is also interesting that once
a thread releases the lock the other( waiting )  thread gets the
mutex. The rule is that if there is a single thread that is waiting
then it will obtain the mutex. Usually the thread that is waiting
the longest will obtain the lock.
We also have a recursive mutex that allows a thread to call the lock on the same mutex multiple times without going into a deadlock.

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

using namespace std ;

class BankAccount {
public:
    BankAccount(int initialBalance) : balance_(initialBalance) {}

    void deposit(int amount) {
        recursiveMutexObject.lock() ;
        balance_ += amount;
        cout << "Deposited " << amount << ". New balance: " << balance_ << endl;
        recursiveMutexObject.unlock() ;
    }

    void withdraw(int amount) {
        recursiveMutexObject.lock() ; // Lock this account
        if (balance_ >= amount) {
            balance_ -= amount;
            cout << "Withdrew " << amount << ". New balance: " << balance_ << endl;
        } else {
            cout << "Insufficient funds to withdraw " << amount << ". Current balance: " << balance_ << endl;
        }
        recursiveMutexObject.unlock() ; // Lock this account
    }

    // This method demonstrates recursive locking
    void transfer(BankAccount& otherAccount, int amount)
    {
        recursiveMutexObject.lock() ; // Lock this account
        cout << "Initiating transfer of " << amount << " from this account." << endl;
        withdraw(amount); // This will re-acquire the lock on 'this' account
        otherAccount.deposit(amount); // This will attempt to acquire the lock on 'otherAccount'
        cout << "Transfer complete." << endl;
        recursiveMutexObject.unlock() ;
    }

    int getBalance() {

        return balance_;
    }

private:
    recursive_mutex recursiveMutexObject   ;
    int balance_;
};

int main() {
    BankAccount account1(1000);
    BankAccount account2(500);

    // Demonstrate recursive locking within a single thread
    account1.transfer(account2, 200);

    cout << "Final balance of account1: " << account1.getBalance() << endl;
    cout << "Final balance of account2: " << account2.getBalance() << endl;

    return 0;
}
We need to be careful to unlock the recursive mutex else another thread trying to acquire the lock will hang. The mutex object also has a "try_lock" method that is useful if we do not want to wait for a long time for a thread to release a lock. The "try_lock" wil attempt to acquire the lock if it can and if it can't then the call returns immediately.

File: mutex2.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

using namespace std ;

mutex myMutex;
int shared_resource = 0;

void worker_function(int id)
{
    // Attempt to acquire the lock
    if (myMutex.try_lock())
    {
        // Lock acquired, access the shared resource
        cout << "Thread " << id << " acquired the lock and is accessing the shared resource." << endl;
        shared_resource++;
        cout << "Shared resource value: " << shared_resource << endl;
        myMutex.unlock(); // Release the lock
    } else {
        // Lock not acquired, perform an alternative action
        cout << "Thread " << id << " could not acquire the lock. Performing alternative action." << endl;
        // Simulate some other work
        this_thread::sleep_for(chrono::milliseconds(50));
    }
}

int main()
{
    vector<thread> threads;
    for (int i1 = 0; i1 < 5; ++i1)
    {
        threads.emplace_back(worker_function, i);
    }

    for (auto& t : threads) {
        t.join();
    }

    cout << "Final shared resource value: " << shared_resource << endl;
    return 0;
}

Exercise

1) Sometimes a multi-threaded program seems to work fine but is not logically correct. Run the below program a few times to make sure that the final correct value of "500" is printed out. Then uncomment the "cout" statement and observe the incorrect result. Explain what you think is going ? And fix the issue by using a mutex.

File: mutex_ex_1.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>

using namespace std ;


int shared_data = 0; // Shared resource

void increment_shared_data()
{
    for (int i1 = 1; i1 <= 100; ++i1 )
    {
        //TO DO Fix the issue Hint: use mutex
        int temp = shared_data ;
        //cout << "Value of i1:" << i1 << endl ;
        shared_data = temp + 1 ;
    }
}

int main()
{
    vector<thread> threads;

    // Create multiple threads that will concurrently modify shared_data
    for (int i1 = 0; i1 < 5; ++i1)
    {
        threads.push_back( thread(increment_shared_data) );
    }

    // Join all threads to wait for their completion
    for (thread& t1 : threads) {
        t1.join();
    }

    cout << "Final value of shared_data: " << shared_data << endl;

    return 0;
}
2) The below program seems to hang. Explain what's going on and fix the problem

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

using namespace std ;

mutex gmutex ;

void thread_function1( int id )
{

    for( int i1=1 ; i1<=5 ; i1++ )
     {
        gmutex.lock() ;
        cout << "Thread id: " << id << " i1:" <<
          i1 << endl ;
     }

}

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;
}
3)The below program prints something but never ends. Explain what's going on.

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

using namespace std ;

mutex mutex1 ;
mutex mutex2 ;

void thread_function1()
{
   cout << "Enter Thread function1: " << endl ;
   mutex1.lock() ;
   this_thread::sleep_for(chrono::milliseconds(1000));
   mutex2.lock() ;
   mutex1.unlock() ;
   mutex2.unlock() ;
   cout << "Exiting Thread function1: " << endl ;
}

void thread_function2()
{
   cout << "Enter Thread function2: " << endl ;
   mutex2.lock() ;
   this_thread::sleep_for(chrono::milliseconds(1000));
   mutex1.lock() ;

   mutex2.unlock() ;
   mutex1.unlock() ;
   cout << "Exiting Thread function2: " << 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;
}
4) Explain what's going on in the following program ?

File: mutex_ex_4.cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono> // For chrono::milliseconds

using namespace std ;

mutex myMutex;
int shared_data = 0;

void worker_function(int id)
{
    for (int i1 = 1; i1 <= 5; ++i1)
    {
        // Attempt to acquire the lock
        if (myMutex.try_lock())
        {
            // Lock acquired, perform critical section operations
            shared_data++;
            cout << "Thread " << id << " acquired lock, shared_data: " << shared_data << endl;
            myMutex.unlock(); // Release the lock
        }
        else
        {
            // Lock not acquired, perform alternative action or retry later
            cout << "Thread " << id << " could not acquire lock, doing something else..." << endl;
            // Simulate doing something else for a short period before retrying
            i1-- ;
            this_thread::sleep_for(chrono::milliseconds(50));
        }
        this_thread::sleep_for(chrono::milliseconds(100)); // Simulate some work
    }
}

int main()
{
    thread t1(worker_function, 1);
    thread t2(worker_function, 2);
    thread t3(worker_function, 3);

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

    cout << "Final shared_data: " << shared_data << endl;

    return 0;
}
















































Solutions

1)

File: mutex_ex_1s.cpp
2)

File: mutex_ex_2s.cpp