C++11 -- multithreaded programming 5

Translation from: https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/

In this article, we will discuss how to use mutexes to protect shared data in a multithreaded environment and avoid race conditions.

In order to repair the contention condition in a multithreaded environment, we need a mutex, that is, each thread needs to lock a mutex before modifying or reading shared data, and each thread should unlock the mutex after modifying data.

In the C++11 thread library, the mutex is located in the < mutex > header file. The class representing the mutex is the std::mutex class.

Mutex has two important methods:
1.) lock()
2.) unlock()

In this article, we'll see how to use std::mutex to fix race conditions in a multithreaded wallet.

Since Wallet provides the service of adding money to Wallet, and different threads use the same Wallet object, we need to add Lock in the addMoney() method of Wallet, that is
Acquire the lock before adding Wallet's money and release the lock function before leaving. Let's look at the code

Wallet class that internally maintains currency and provides services / functions, i.e. addMoney().
This member function first obtains the lock, then increments the internal currency of the wallet object according to the specified count, and then releases the lock.

#include<iostream>
#include<thread>
#include<vector>
#include<mutex>
class Wallet
{
    int mMoney;
    std::mutex mutex;
public:
    Wallet() :mMoney(0) {}
    int getMoney() { return mMoney; }
    void addMoney(int money)
    {
        mutex.lock();
        for (int i = 0; i < money; ++i)
        {
            mMoney++;
        }
        mutex.unlock();
    }
};
int testMultithreadedWallet()
{
    Wallet walletObject;
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.push_back(std::thread(&Wallet::addMoney, &walletObject, 1000));
    }
    for (int i = 0; i < threads.size(); i++)
    {
        threads.at(i).join();
    }
    return walletObject.getMoney();
}
int main()
{
    int val = 0;
    for (int k = 0; k < 1000; k++)
    {
        if ((val = testMultithreadedWallet()) != 5000)
        {
            std::cout << "Error at count = " << k << "  Money in Wallet = " << val << std::endl;
            //break;
        }
    }
    return 0;
}

Ensure that you will not find a single scene in which the money in your wallet is less than 5000.
Because the mutex in addMoney ensures that once one thread completes the money modification, only other threads modify the money in the wallet.

But what if we forget to unlock the mutex at the end of the function? In this case, one thread will exit without releasing the lock, and other threads will continue to wait.
This can happen when an exception occurs after locking the mutex. To avoid this, we should use std::lock_guard.

class Wallet
{
    int mMoney;
    std::mutex mutex;
public:
    Wallet() :mMoney(0){}
    int getMoney()   {     return mMoney; }
    void addMoney(int money)
    {
        std::lock_guard<std::mutex> lockGuard(mutex);
        // In constructor it locks the mutex
        for(int i = 0; i < money; ++i)
        {
            // If some exception occurs at this
            // poin then destructor of lockGuard
            // will be called due to stack unwinding.            mMoney++;
        }
        // Once function exits, then destructor
        // of lockGuard Object will be called.
        // In destructor it unlocks the mutex.
    }
 };

 

Tags: C++11

Posted on Mon, 08 Nov 2021 06:22:20 -0500 by depraved