C++ atomic 01
Table of contents
Atomic operations is an indivisible operation. We cannot observe such an operation half-done from any thread in the systesm.
Single line statement does not means single operation
Steps required for i++;
- step 1: read the value of
i
from the memory - step 2: increment that value by one
- step 3: store the modified value to the memory
atomic_flag
Most basic type, it is like a bool.
Operations:
clear()
test_and_set()
atomic_flag
should be initialized with ATOMIC_FLAG_INIT
::: warning
The atomic_flag
is the unique type that does not provide
is_lock_free
function
void demo_atomic_flag()
{
fmt::print( "==== {} ====\n", __FUNCTION__ );
std::atomic_flag flag1 = ATOMIC_FLAG_INIT;
fmt::print( "#1 The previous value of flag1 is {}\n", flag1.test_and_set() ); // false
fmt::print( "#2 The previous value of flag1 is {}\n", flag1.test_and_set() ); // true
fmt::print( "#3 The previous value of flag1 is {}\n", flag1.test_and_set() ); // true
flag1.clear();
fmt::print( "#4 The previous value of flag1 is {}\n", flag1.test_and_set() ); // false
}
atomic_bool
- neither copy assignable nor copy constructible
- can assign non atomic booleans
- can be construct using non atomic booleans
void demo_atomic_bool()
{
fmt::print( "==== {} ====\n", __FUNCTION__ );
// clang false by default
// msvs true by default
std::atomic<bool> flag1;
fmt::print( "The value of atomic<bool> flag1 is {}\n", flag1.load() ); // false
fmt::print( "Is lock free: {}\n", flag1.is_lock_free() );
// Compilation error std::atomic<bool> flag2(flag1);
// Compilation error std::atomic<bool> flag3 = flag1;
bool non_atomic_bool{ true };
std::atomic<bool> flag4( non_atomic_bool );
fmt::print( "The value of atomic<bool> flag4 is {}\n", flag4.load() ); // true
std::atomic<bool> flag5 = false;
fmt::print( "The value of atomic<bool> flag5 is {}\n", flag5.load() ); // false
flag5.store( true );
fmt::print( "The value of atomic<bool> flag5 is {} after store\n", flag5.load() ); // true
auto previous_value = flag5.exchange( false );
fmt::print( "previous_value is {} and flag5 is {}\n", previous_value, flag5.load() ); // false
}
atomic_int
std::atomic<int> x{0};
++x; // atomic pre-increment
x++; // atomic post-incrment
x += 1; // atomic increment
x |= 2; // atomic bit set
int y = x * 2; // atomic read of x
x = y + 1; // atomic write
x = x + 1; // atomic read followed by atomic write
x = x * 2; // atomic read followed by atomic write
---- x *= 2; // DOES NOT EXIST ATOMIC MULTIPLICATION
Compare exchange
bool r = X.compare_exchange_weak( T& expected, T desired )
bool r = X.compare_exchange_strong( T& expected, T desired )
- Compare the value of the atomic variable
X
withexpected
value and stores thedesired
value if they are equal. - If they are not equal the
expected
value is updated with the actual value of the atomic variable. - Return true if
store
is performed and false otherwise.
weak: if the CPU does not have a simple compare exchange instruction is not possible to garantee the atomicity of the operation. More performant.
strong: to garantee the atomicity this function may require more CPU instructions.
See Compare and swap.
This is the logic in the Intel Software Manual Vol 2A:
bool compare_and_swap( int *accum, int *dest, int newval )
{
if( *accum == *dest ) {
*dest = newval;
return true;
}
else {
*accum = *dest;
return false;
}
}
void demo_atomic_int()
{
fmt::print( "==== {} ====\n", __FUNCTION__ );
std::atomic<int> x1( 20 );
int expected_value_1 = 20;
int desired_value_1 = 6;
fmt::print( "#1 value of x1 is {}\n", x1.load() );
bool r1 = x1.compare_exchange_weak( expected_value_1, desired_value_1 );
fmt::print( "#2 value of x1 is {}\n", x1.load() );
fmt::print( "#3 value of desired_value_1 is {}\n", desired_value_1 );
fmt::print( "#4 value of expected_value_1 is {}\n", expected_value_1 );
fmt::print( "#5 is store executed? {}\n", r1 );
std::atomic<int> x2( 10 );
int expected_value_2 = 20;
int desired_value_2 = 7;
fmt::print( "#6 value of x2 is {}\n", x1.load() );
bool r2 = x2.compare_exchange_weak( expected_value_2, desired_value_2 );
fmt::print( "#7 value of x2 is {}\n", x2.load() );
fmt::print( "#8 value of desired_value_2 is {}\n", desired_value_2 );
fmt::print( "#9 value of expected_value_2 is {} (updated)\n", expected_value_2 );
fmt::print( "#10 is store executed? {}\n", r2 );
}
Use constructor instead of std::atomic<unsigned long> sum1 = 0
.
atomic<unsigned long> sum1( 0 );
void doWork1( size_t N, unsigned long *a )
{
for( size_t i = 0; i < N; ++i ) {
sum1 += a[i];
}
}
unsigned long sum2( 0 );
mutex M;
void doWork2( size_t N, unsigned long *a )
{
unsigned long s = 0;
for( size_t i = 0; i < N; ++i ) {
s += a[i];
}
lock_guard<mutex> L( M );
sum2 += s;
}
int main()
{
demo_atomic_flag();
demo_atomic_bool();
demo_atomic_int();
// Size of array
constexpr size_t N = 10000;
array<unsigned long, N> data;
// Array initialization
iota( data.begin(), data.end(), 1000 );
thread t1( doWork1, N, data.data() );
thread t2( doWork2, N, data.data() );
t1.join();
t2.join();
cout << "Sum1: " << sum1 << endl;
cout << "Sum2: " << sum2 << endl;
return 0;
}
Possible output
==== demo_atomic_flag ====
#1 The previous value of flag1 is false
#2 The previous value of flag1 is true
#3 The previous value of flag1 is true
#4 The previous value of flag1 is false
==== demo_atomic_bool ====
The value of atomic<bool> flag1 is false
Is lock free: true
The value of atomic<bool> flag4 is true
The value of atomic<bool> flag5 is false
The value of atomic<bool> flag5 is true after store
previous_value is true and flag5 is false
==== demo_atomic_int ====
#1 value of x1 is 20
#2 value of x1 is 6
#3 value of desired_value_1 is 6
#4 value of expected_value_1 is 20
#5 is store executed? true
#6 value of x2 is 6
#7 value of x2 is 10
#8 value of desired_value_2 is 7
#9 value of expected_value_2 is 10 (updated)
#10 is store executed? false
Sum1: 59995000
Sum2: 59995000