C ++ 11 Multithreading-Part 5: Using locks to resolve race conditions

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

In this article, we will discuss how to use mutex locks in a multi-threaded environment to protect shared data and avoid race conditions.

In order to fix the contention conditions in a multi-threaded environment, we need a mutex, that is, each thread needs to lock the mutex before modifying or reading shared data. After modifying the data, each thread should unlock the mutex.

std::mutex

In the C ++ 11 thread library, the mutex is located Header file. The class representing the mutex is the std :: mutex class.
There are two important methods of mutual exclusion:

1.)   lock()
2.)  unlock()

In the previous article, we have explained the contention conditions using multi-threaded Wallet.

In this article, we will see how to use std :: mutex to resolve contention conditions in multi-threaded wallets.
Since Wallet provides a service to add money in Wallet and use the same Wallet object between different threads, you need to add a lock method to the addMoney () method in the Wallet class, namely:

在增加钱包(Wallet)里的钱之前获取锁,并在离开该功能之前释放锁。让我们看看代码,
钱包类,在内部维护货币并提供服务/功能,即addMoney()。
此成员函数首先获取一个锁,然后按指定的计数递增钱包对象的内部货币,然后释放锁。
#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();
    }
};

Now let's create 5 threads, all of which will share the same object of a Wallet class and use their addMoney () member function to add 1000 to the internal money in parallel.
So, if the initial money in the wallet is 0. After completing the execution of all threads, the money in the wallet should be 5000.
And this mutual exclusion lock guarantees that the money in the wallet is finally 5000.
Let's test it,

#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;
}

It guarantees that it will not find a scenario where the money in the wallet is less than 5000.
Because the mutex in addMoney ensures that once a thread completes the money modification, only any other thread modifies 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 the other threads will remain waiting.
This may happen if an exception occurs after locking the mutex. To avoid this, we should use std :: lock_guard.

std::lock_guard

std :: lock_guard is a class template that implements RAII for mutex locks.
It wraps the mutex in its object and locks the attached mutex in its constructor. When the destructor is called, it releases the mutex.
Let's look at the code,

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.
    }
 };

Guess you like

Origin www.cnblogs.com/vivian187/p/12738787.html