如何在 C++ 中使用原子来防止临界区访问
How to prevent critical section access with atomics in C++
我有一个程序,其中 4 个线程在两个“银行账户”之间进行随机交易,每个账户每进行 100 笔交易,应向该账户添加 5% 的利息。
这是我必须做的学校练习,在上一课中,我们使用了互斥锁,锁定一段代码似乎非常简单。
现在我们必须做同样的事情,但使用原子。
我的问题是目前不止一个线程可以进入addIntreset
函数,兴趣可以多次添加
这是我检查是否是第 100 笔交易的代码的一部分
void transfer(Account& acc, int amount, string& thread)
{
++m_iCounter;
withdraw(acc, amount);
if (m_iCounter % 100 == 0)
{
addInterest(thread);
}
}
所有线程都通过该函数并检查 %100,有时不止一个线程进入 addInterest。使用互斥锁,我可以在此处限制访问,但我如何使用原子来做到这一点?
也许我可以在 addInterest
中以某种方式解决这个问题,但如果可以的话怎么办?
addInterest
内部是以下代码(如果多次添加超过 1 个感兴趣的线程):
void addInterest(string& thread)
{
float old = m_iBalance.load();
const float interest = 0.05f;
const float amount = old * interest;
while (!m_iBalance.compare_exchange_weak(old, old + amount))
{
//
}
++m_iInterest;
cout << thread << " interest : Acc" << name << " " << m_iCounter << endl;
}
好的 2 个可能的解决方案:
第一个是使用header<atomic>
中的atomic
模板,在这种情况下你至少可以制作m_iCounter
作为一个原子变量。但是为了更好的操作原子性,关键部分中的所有变量都必须是原子的。
进一步阅读 Atomic and Atomic template。
第二个选项是使用 c++ 的实验特性,即所谓的 STM(软件事务内存)。在这种情况下,transfer
函数的所有内容都可以是一个事务,或者无论如何都是您之前互斥的所有内容。
延伸阅读 Transactional Memory c++
一个问题是您分别递增和读取计数器,因此 addInterest
可以被多次调用。要解决此问题,您应该以原子方式写入和读取。
const int newCounter = ++m_iCounter;
增加兴趣:
如果添加利息并不重要,只要它发生在增加计数器之后,那么您的 addInterest
可能就没问题了。
如果您必须在第 100 笔交易期间为您的余额增加利息(即使在该线程到达 addInterest
时它已经改变),您必须在增加计数器之前以某种方式存储旧余额。我能想到的唯一方法是使用原子标志代替互斥锁来同步整个 transfer
:
// Member variable
std::atomic_bool flag;
// Before the critical section
bool expected;
do {
expected = false;
} while (!flag.compare_exchange_weak(expected, true));
// Critical section here
// Unlock at the end
flag = false;
我有一个程序,其中 4 个线程在两个“银行账户”之间进行随机交易,每个账户每进行 100 笔交易,应向该账户添加 5% 的利息。
这是我必须做的学校练习,在上一课中,我们使用了互斥锁,锁定一段代码似乎非常简单。 现在我们必须做同样的事情,但使用原子。
我的问题是目前不止一个线程可以进入addIntreset
函数,兴趣可以多次添加
这是我检查是否是第 100 笔交易的代码的一部分
void transfer(Account& acc, int amount, string& thread)
{
++m_iCounter;
withdraw(acc, amount);
if (m_iCounter % 100 == 0)
{
addInterest(thread);
}
}
所有线程都通过该函数并检查 %100,有时不止一个线程进入 addInterest。使用互斥锁,我可以在此处限制访问,但我如何使用原子来做到这一点?
也许我可以在 addInterest
中以某种方式解决这个问题,但如果可以的话怎么办?
addInterest
内部是以下代码(如果多次添加超过 1 个感兴趣的线程):
void addInterest(string& thread)
{
float old = m_iBalance.load();
const float interest = 0.05f;
const float amount = old * interest;
while (!m_iBalance.compare_exchange_weak(old, old + amount))
{
//
}
++m_iInterest;
cout << thread << " interest : Acc" << name << " " << m_iCounter << endl;
}
好的 2 个可能的解决方案:
第一个是使用header
<atomic>
中的atomic
模板,在这种情况下你至少可以制作m_iCounter
作为一个原子变量。但是为了更好的操作原子性,关键部分中的所有变量都必须是原子的。 进一步阅读 Atomic and Atomic template。第二个选项是使用 c++ 的实验特性,即所谓的 STM(软件事务内存)。在这种情况下,
transfer
函数的所有内容都可以是一个事务,或者无论如何都是您之前互斥的所有内容。 延伸阅读 Transactional Memory c++
一个问题是您分别递增和读取计数器,因此 addInterest
可以被多次调用。要解决此问题,您应该以原子方式写入和读取。
const int newCounter = ++m_iCounter;
增加兴趣:
如果添加利息并不重要,只要它发生在增加计数器之后,那么您的 addInterest
可能就没问题了。
如果您必须在第 100 笔交易期间为您的余额增加利息(即使在该线程到达 addInterest
时它已经改变),您必须在增加计数器之前以某种方式存储旧余额。我能想到的唯一方法是使用原子标志代替互斥锁来同步整个 transfer
:
// Member variable
std::atomic_bool flag;
// Before the critical section
bool expected;
do {
expected = false;
} while (!flag.compare_exchange_weak(expected, true));
// Critical section here
// Unlock at the end
flag = false;