Sharing Data & Race Conditions

Sharing Data & Race Conditions

多线程编程的一个经典问题就是Race Condition

What is a Race Condition?

race condition是多线程编程的一个经典bug,它指的是当多个线程并行执行某些操作时,它们访问公共的内存区域。此时有若干个线程回去修改这篇内存区域的的数据。

A Practical example of Race Condition

假设有这样的一个钱包类,我们提供一个成员函数addMoney():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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个线程,共享这个钱包类的对象,并行地添加1000到哪步的money变量,预期结果钱包类的结果应该是5000。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
}

但由于并发时,mMoney++并不是原子操作,而是分为三个机器指令:

  • 从存储加载mMoney到寄存器;
  • 递增寄存器的值;
  • 把寄存器的值更新回存储;

以下是出现的几种非预期结果:

1
2
3
4
5
6
Error at count = 14 Money in Wallet = 4000
Error at count = 44 Money in Wallet = 4112
Error at count = 52 Money in Wallet = 4387
Error at count = 65 Money in Wallet = 4904
Error at count = 81 Money in Wallet = 4907
Error at count = 98 Money in Wallet = 4666