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

解决方法

一个问题是您分别递增和读取计数器,因此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;
,

确定2种可能的解决方案:

  • 第一个是使用标头atomic中的<atomic>模板,在这种情况下,您至少可以将m_iCounter用作原子变量。但是为了获得更好的操作原子性,关键部分中的所有变量都必须是原子性的。 进一步阅读AtomicAtomic template

  • 第二个选项是使用c ++的临时功能,即所谓的 STM (软件事务存储)。在这种情况下,transfer函数的所有内容都可以是事务,也可以是您之前已互斥的所有东西。 进一步阅读Transactional Memory c++