C++11 多线程 – Part 5:使用锁解决争用条件

翻译自:https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/

在本文中,我们将讨论如何在多线程环境中使用互斥锁来保护共享数据,并避免争用条件。

为了修复多线程环境中的争用条件,我们需要互斥锁,即每个线程在修改或读取共享数据之前都需要锁定互斥锁,修改数据之后,每个线程都应该解锁互斥锁。

std::mutex

在C++11线程库中,互斥体位于头文件中。表示互斥对象的类是std::mutex类。
互斥有两个重要的方法:

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

在前一篇文章中,我们已经使用多线程Wallet解释了争用条件。

在本文中,我们将看到如何使用std::mutex解决多线程wallet中的争用条件。
由于Wallet提供了在Wallet中添加钱的服务,并且在不同线程之间使用相同的Wallet对象,所以需要在Wallet类中的addMoney() 方法中添加锁方法,即:

在增加钱包(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();
    }
};

现在让我们创建5个线程,所有这些线程将共享一个Wallet类的相同对象,并使用其addMoney()成员函数并行向内部money添加1000个。
所以,如果最初钱包里的钱是0。完成所有线程的执行后,钱包里的钱应该是5000。
而且这个互斥锁保证钱包里的钱最终是5000。
我们来测试一下,

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

它保证不会找到一个钱包里的钱少于5000的场景。
因为addMoney中的互斥锁确保一旦一个线程完成money的修改,那么只有任何其他线程修改Wallet中的money。
但是如果我们在函数结束时忘记解锁互斥锁呢。在这种情况下,一个线程将退出而不释放锁,其他线程将保持等待状态。
如果锁定互斥锁后出现异常,则可能会发生这种情况。为了避免这种情况,我们应该使用std::lock_guard。

std::lock_guard

std::lock_guard是一个类模板,它实现了互斥锁的RAII。
它将互斥锁包装在其对象中,并将附加的互斥锁锁定在其构造函数中。当调用析构函数时,它会释放互斥锁。
让我们看看代码,

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

原文链接: https://www.cnblogs.com/vivian187/p/12738787.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    C++11 多线程 - Part 5:使用锁解决争用条件

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/397107

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月7日 上午9:16
下一篇 2023年4月7日 上午9:16

相关推荐