简体   繁体   中英

How to prevent critical section access with atomics in C++

I have a program where 4 threads are doing random transactions between two "bank accounts", at every 100th transaction per account interest of 5% should be added to the account.

This is an exercise for school I have to do, and in the previous lesson, we used mutexes which seemed very straightforward in locking a section of code. Now we have to do the same thing but using atomics.

My problem is that currently more than one thread can enter addIntreset function and interest can be added multiple times.

this is part of the code where I check if it is 100th transaction

void transfer(Account& acc, int amount, string& thread)
{
    ++m_iCounter;

    withdraw(acc, amount);
    if (m_iCounter % 100 == 0)
    {
        addInterest(thread);
    }
}

All threads go through that function and check for % 100 and sometimes more than one thread slips into addInterest. With mutexes I could restrict access here but how do I do it with atomic?

Maybe I could solve this somehow inside addInterest but if so how? Inside addInterest is following code (if more than 1 thread slip in interest is added multiple times) :

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;
}

One problem is that you increment and read counter separately, and because of this addInterest can be called multiple times. To fix this, you should write and read atomically.

const int newCounter = ++m_iCounter;

Adding interest:

If it doesn't matter when you add interest, as long as it happens after incrementing the counter, then your addInterest is probably fine as is.

If you must add interest to the balance you had during the 100th transaction (even if it has already changed when that thread reaches addInterest ), you must somehow store the old balance before you increment the counter. The only way I can think of is to synchronize the entire transfer by using atomic flag as a replacement for mutex:

// 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;

Ok 2 possible solution:

  • the first one is to use the atomic template that is in the header <atomic> , in this case you can make at least the m_iCounter as an atomic variable. but for a better atomicity of the operation all the variables in a critical section have to be atomic. Further read on Atomic and Atomic template .

  • the second option is use a sperimental feature of c++ the so called STM (Software Transactional Memory). In this case all the content of the transfer function could be a transaction, or anyway all the thinghs that you have mutexed before. Further readingTransactional Memory c++

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM