翻译来自:C++11 Multithreading – Part 4: Data Sharing and Race Conditions
在多线程环境中,线程之间的数据共享非常容易。但是,这种易于共享的数据可能会导致应用程序出现问题。这样的问题之一就是比赛条件。
什么是比赛条件?
竞争条件是一种在多线程应用程序中发生的错误。
当两个或多个线程并行执行一组操作时,它们将访问同一内存位置。同样,其中的一个或多个线程会修改该内存位置中的数据,这有时会导致意外结果。
这称为竞争条件。
竞赛条件通常不会每次都发生,因此通常很难找到和复制。仅当两个或多个线程的相对执行顺序导致意外结果时,它们才会发生。让我们通过一个例子来理解,
竞赛条件的实际示例:
让我们创建一个Wallet类,该类在内部维护金钱并提供服务/功能,即addMoney()。该成员函数使钱包对象的内部货币增加指定的数量。
class Wallet
{
int mMoney;
public:
Wallet() :mMoney(0){}
int getMoney() { return mMoney; }
void addMoney(int money)
{
for(int i = 0; i < money; ++i)
{
mMoney++;
}
}
};
现在让我们创建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;
}
}
return 0;
}
由于同一Wallet类对象的addMoney()成员函数执行了5次,因此其内部货币预计为5000。但是由于addMoney()成员函数并行执行,因此在某些情况下mMoney会比5000小得多,即
Output is,
Error at count = 971 Money in Wallet = 4568 Error at count = 971 Money in Wallet = 4568 Error at count = 972 Money in Wallet = 4260 Error at count = 972 Money in Wallet = 4260 Error at count = 973 Money in Wallet = 4976 Error at count = 973 Money in Wallet = 4976
这是一种竞争条件,因为这里有两个或更多线程试图同时修改同一内存位置并导致意外结果。
为什么会这样?
每个线程并行增加相同的“ mMoney”成员变量。尽管似乎只有一行,但实际上此“ mMoney ++”已转换为三个机器命令,
- 将“ mMoney”变量值加载到寄存器中
- 增量寄存器的值
- 用寄存器的值更新变量“ mMoney”
现在假设在特殊情况下,上述命令的执行顺序如下:
在这种情况下,一个增量将被忽略,因为不是将两次“ mMoney”变量递增一次,而是将不同的寄存器递增,并且“ mMoney”变量的值被覆盖。
假定在此方案之前,mMoney为46,如上图所示,它增加了2倍,因此预期结果为48。但是由于在上述方案中的竞争条件,mMoney的最终值将仅为47。
这称为竞争条件。
如何解决比赛条件?
为了解决这个问题,我们需要使用Lock机制,即每个线程需要在修改或读取共享数据之前获得一个锁,并且在修改数据之后,每个线程都应该解锁该锁。
我们将在下一篇文章中讨论这一点,即
https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/