I am wondering if you can pass a an atomic by reference to a thread and for the .load and .store operations still be thread safe. For instance:
#include <thread>
#include <atomic>
#include <cstdlib>
void addLoop(std::atomic_int& adder)
{
int i = adder.load();
std::srand(std::time(0));
while(i < 200)
{
i = adder.load();
i += (i + (std::rand() % i));
adder.store(i);
}
}
void subLoop (std::atomic_int& subber)
{
int j = subber.load();
std::srand(std::time(0));
while(j < 200)
{
j = subber.load();
j -= (j - (std::rand() % j));
subber.store(j);
}
}
int main()
{
std::atomic_int dummyInt(1);
std::thread add(addLoop, std::ref(dummyInt));
std::thread sub(subLoop, std::ref(dummyInt));
add.join();
sub.join();
return 0;
}
When the addLoop thread stores the new value into the atomic if subLoop were to access it using the load and store functions would it end up being an undefined state?
If you have used std::thread or std::bind , you probably noticed that even if you pass a reference as parameter, it still creates a copy instead. From cppreference, The arguments to the thread function are moved or copied by value.
Yes, it would be threadsafe. Assuming of course there are no bugs in the std::atomic implementation - but it's not usually hard to get right. This is exactly what std::atomic is meant to do.
In c++11 to pass a referenceto a thread, we have std::ref(). std::thread t3(fun3, std::ref(x)); In this statement we are passing reference of x to thread t3 because fun3() takes int reference as a parameter.
Summing up, in general atomic operations are faster if contention between threads is sufficiently low.
According to [intro.races]/20.2,
The execution of a program contains a data race if it contains two potentially concurrent conflicting actions, at least one of which is not atomic, and neither happens before the other, except for the special case for signal handlers described below. Any such data race results in undefined behavior.
According to [intro.races]/2,
Two expression evaluations conflict if one of them modifies a memory location (4.4) and the other one reads or modifies the same memory location.
Accessing an atomic variable through a reference doesn't introduce any additional accesses or modifications, because references do not occupy memory locations. Therefore, performing potentially concurrent atomic operations is still safe when those operations occur through references.
In fact, in the abstract model of evaluation in C++, there is no difference between accessing an object by its name and accessing an object through a reference variable bound to that object. Both are merely lvalues referring to the object.
Beware of the std::atomic_init
function, which is non-atomic:
std::atomic<int> x;
void f(std::atomic<int>& r) {
std::atomic_init(&r, 0);
}
void g(std::atomic<int>& r) {
r = 42;
}
In the above code, if f
and g
run in separate threads and both access the atomic variable x
, a data race can occur, because one of the operations is not atomic. However, this is no different from triggering the data race like so:
std::atomic<int> x;
void f() {
std::atomic_init(&x, 0);
}
void g() {
x = 42;
}
where no references are involved.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With