如何在 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;