翻译来自:C++11 Multithreading – Part 5: Using mutex to fix Race Conditions
在本文中,我们将讨论如何使用互斥锁来保护多线程环境中的共享数据并避免竞争情况。
为了解决多线程环境中的竞争条件,我们需要互斥锁,即每个线程都需要在修改或读取共享数据之前锁定互斥锁,并且在修改数据之后,每个线程都应解锁互斥锁。
std :: mutex
在C ++ 11线程库中,互斥锁位于头文件中。代表互斥量的类是std :: mutex类。
互斥锁有两种重要的方法:
1.)lock()
2.)unlock()
我们已经在上一篇文章中使用多线程钱包解释了竞争条件,即
https://blog.csdn.net/qq_43690118/article/details/108308593
在本文中,我们将看到如何使用std :: mutex修复该多线程钱包中的竞争条件。
由于电子钱包提供了在电子钱包中添加资金的服务,并且在不同线程之间使用了相同的电子钱包对象,因此我们需要在电子钱包的addMoney()方法中添加锁定,即
在增加电子钱包的货币之前获取锁并在离开该钱包之前释放锁功能。让我们看一下代码,
内部维护货币并提供服务/功能的钱包类,即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()成员函数并行向内部货币添加1000。
因此,如果最初在钱包中的钱为0。那么在完成所有线程的执行后,在Wallet中的钱应该为5000。
而且此互斥锁可确保电子钱包中的资金最终为5000。
让我们测试一下
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中的互斥锁可确保一旦一个线程完成了钱的修改,则只有其他任何线程才能修改Wallet中的钱。
但是,如果我们忘记在功能结束时解锁互斥锁,该怎么办?在这种情况下,一个线程将退出而不释放锁,而其他线程将保持等待状态。
如果锁定互斥锁后发生某些异常,则可能发生这种情况。为了避免这种情况,我们应该使用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.
}
};