Understanding atomic variables and compare_exchange in multithreading C++

  atomicity, c++, multithreading

I have a program that simulates two bank accounts and 4 threads are applying withdraw and deposit transactions randomly between the accounts.

Every 100 th transaction in account, an interest (5%) should be added to the current account balanace (account += account * 0.05).

This is one of my school exercises and I think I’m almost there.
with deposit() and withdraw() functions I believe I understand that doing while loop with compare_exchange is a must as each thread has to finish the transaction. I think this is working as it must.

However, regarding the 100th transaction interest I am not sure is everything working as intended, code for addInterest function currently is as follows

    void addInterest(string& thread)
{       
    if (m_bDone.load() == false)
    {
        m_bDone.store(true);
        const auto interest = 0.05f;
        float old = m_iBalance.load();
        while (!m_iBalance.compare_exchange_strong(old, old + old * interest))
        {
            // 
        }
        m_bDone.store(false);           
    }
    cout << thread << " interest ; " << name << " " << m_iBalance << endl;
}

What I try to do is that when one thread reached this function it sets atmoic m_bDone to true, so other threads would skip this, then perform addInterest and set m_bDone to false again for next 100th occurance.

My questions :

  1. With my current code, am I achieving that only one thread will do the addInterest?
  2. If not, what am I missing and how can I fix it ?

I ponder that maybe it is possible that one thread will perform addInterest but another one comes in just after first thread finished at set m_bDone false again. In that case interest would be added twice.

Or two or more threads reach m_bDone.load() == false simultaneously and each performs addInterest.
Also I have compare_exchange on m_iBalance, is it appropriately placed where it is.

Full code below:

#include <iostream>
#include <thread>
#include <functional>
#include <random>

using namespace std;

class Account
{
public:
    Account(string _name)
    {
        name = _name;
        m_iBalance = 1000.f;
        m_iCounter = 0;
        m_bDone = false;
    }

    float checkBalance() const
    {
        return m_iBalance;
    }

    void transfer(Account& acc, int amount, string& thread)
    {
        if (m_iBalance - amount < 0)
        {
            // count low funds transfer attempt as counter++
            m_iCounter++;
            return;
        }

        {
            m_iCounter++;
            cout << m_iCounter << " " << name << endl;
            
            withdraw(amount);
            acc.deposit(amount);
            if (m_iCounter % 100 == 0)
            {
                addInterest(thread);
            }
            
        
        }

    }

    void deposit(int amount)
    {
        float old = m_iBalance.load();
        while (!m_iBalance.compare_exchange_weak(old, old + amount))
        {
            // 
        }   
    }

    void withdraw(int amount)
    {
        float old = m_iBalance.load();
        while (!m_iBalance.compare_exchange_weak(old, old - amount))
        {
            // 
        }
    }

    void addInterest(string& thread)
    {       
        if (m_bDone.load() == false)
        {
            m_bDone.store(true);
            const auto interest = 0.05f;
            float old = m_iBalance.load();
            while (!m_iBalance.compare_exchange_strong(old, old + old * interest))
            {
                // 
            }
            m_bDone.store(false);           
        }
        cout << thread << " interest ; " << name << " " << m_iBalance << endl;
    }

    int checkCounter() const
    {
        return m_iCounter;
    }

    string name;

private:
    atomic<float> m_iBalance;
    atomic<int> m_iCounter;
    atomic<bool> m_bDone;
};

size_t randomNumber(size_t min, size_t max)
{
    mt19937 rng;
    rng.seed(random_device()());
    uniform_int_distribution<mt19937::result_type> dist(min, max);
    return dist(rng);
}

void executeOperation(Account& acc1, Account& acc2, string& thread)
{
    const auto operation = randomNumber(1, 2);
    switch (operation)
    {
    case 1:
        acc1.transfer(acc2, randomNumber(0, 500), thread);
        break;
    case 2:
        acc2.transfer(acc1, randomNumber(0, 500), thread);
        break;
    default:
        break;
    }
}

void doMultipleOperations(int number,Account& acc1, Account& acc2, string& thread)
{
    for (auto i = 0; i < number; i++)
    {
        executeOperation(acc1, acc2, thread);
    }
    
    cout << "Thread : " << thread << " finished transactions" << endl;
}

int main()
{
    Account acc1("1");
    Account acc2("2");
    
    // Number of transaction to be executed by each thread
    auto number = 200;

    string one = "t1";
    string two = "t2";
    string three = "t3";
    string four = "t4";

    cout << "Starting balance nAccount 1 : " << acc1.checkBalance() << "nAccount 2 : " << acc2.checkBalance() << endl;

    thread t1(doMultipleOperations, ref(number), ref(acc1), ref(acc2), ref(one));
    thread t2(doMultipleOperations, ref(number), ref(acc1), ref(acc2), ref(two));
    thread t3(doMultipleOperations, ref(number), ref(acc1), ref(acc2), ref(three));
    thread t4(doMultipleOperations, ref(number), ref(acc1), ref(acc2), ref(four));

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    cout << "Final balance nAccount 1 : " << acc1.checkBalance() << "nAccount 2 : " << acc2.checkBalance() << endl;
    cout << "Transactions done -> Acc1 : " << acc1.checkCounter() << " Acc2 : " << acc2.checkCounter() << endl;
    
    return 0;
}

Source: Windows Questions C++

LEAVE A COMMENT