C++11 多线程 - Part 5:使用锁解决争用条件
翻译自:https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/
在本文中,我们将讨论如何在多线程环境中使用互斥锁来保护共享数据,并避免争用条件。
为了修复多线程环境中的争用条件,我们需要互斥锁,即每个线程在修改或读取共享数据之前都需要锁定互斥锁,修改数据之后,每个线程都应该解锁互斥锁。
std::mutex
在C++11线程库中,互斥体位于
互斥有两个重要的方法:
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.
}
};