Search code examples
c++multithreadingatomicstdatomiccompare-and-swap

Don't really get the logic of std::atomic::compare_exchange_weak and compare_exchange_strong


I've read https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange

Atomically compares the object representation (until C++20)value representation (since C++20) of *this with that of expected, and if those are bitwise-equal, replaces the former with desired (performs read-modify-write operation). Otherwise, loads the actual value stored in *this into expected (performs load operation).

So as I understand the code like

bool expected=true;
extern atomic<bool> b = false; 
void foo ()
{
//
    while(!b.compare_exchange_weak(expected, false));
//
}

after the loop will run once(ignoring spurious failure) it will fail, and will write to expected false, so on the second iteration compare_exchange_weak will return success, though b hasn't been changes to true. But what's the point of all this? I though I could use this as a lock for synchronization, waiting for other thread to change b, but now I can't think of a usage of this.

Example from the cppreference also shows that after two calls compare_exchange_strong will succeed.

#include <atomic>
#include <iostream>
 
std::atomic<int>  ai;
 
int  tst_val= 4;
int  new_val= 5;
bool exchanged= false;
 
void valsout()
{
    std::cout << "ai= " << ai
          << "  tst_val= " << tst_val
          << "  new_val= " << new_val
          << "  exchanged= " << std::boolalpha << exchanged
          << "\n";
}
 
int main()
{
    ai= 3;
    valsout();
 
    // tst_val != ai   ==>  tst_val is modified
    exchanged= ai.compare_exchange_strong( tst_val, new_val );
    valsout();
 
    // tst_val == ai   ==>  ai is modified
    exchanged= ai.compare_exchange_strong( tst_val, new_val );
    valsout();
}

Result:

ai= 3  tst_val= 4  new_val= 5  exchanged= false
ai= 3  tst_val= 3  new_val= 5  exchanged= false
ai= 5  tst_val= 3  new_val= 5  exchanged= true

Solution

  • I will give an example where I did used that, since it is very simple one.

    I had atomic which describes available size of something. There was a danger of integer overflow, so I had do checking first before subtracting a value.

    Not exact copy paste of my production code:

    class LockFreeCuncuretSizeLimit {
    public:
        explicit LockFreeCuncuretSizeLimit(size_t available) : mSize{available}
        {}
    
        bool tryAcuqire(size_t s) {
            size_t lastSize = mSize;
            size_t newSize;
            do
            {
                if (lastSize >= s)
                {
                    newSize = lastSize - s;
                }
                else
                {
                    return false;
                }
    
            }
            while (!mSize.compare_exchange_weak(lastSize, newSize));
            return true;
        }
    
        void release(size_t s) {
            mSize += s; // here I do not have worry about integer overflow
        }
    private:
        std::atomic<size_t> mSize;
    };
    

    Now try image do that without compare_exchange_strong and not having a race condition.

    There is a change that condition is meet, but when subtraction is done on atomic some other thread already subtracted a value so when I do actual subtraction it is possible to overflow integer. So this cant be done without compare_exchange_strong.

    Now difference between compare_exchange_strong and compare_exchange_weak is hard to explain. Even Herb Sutter on some cppcon talk give up explaining that and provided simple rule: "if you need loop use compare_exchange_weak otherwise use compare_exchange_strong".