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
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
".