Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ 11 can you safely pass and access std::atomics by reference in different threads

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?

like image 352
scholarhedge Avatar asked Jan 31 '18 21:01

scholarhedge


People also ask

What will happen if passing reference through std :: thread?

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.

Is std :: atomic thread safe?

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.

Can you pass a reference to a thread C++?

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.

Is Atomic faster than mutex?

Summing up, in general atomic operations are faster if contention between threads is sufficiently low.


1 Answers

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.

like image 197
Brian Bi Avatar answered Sep 20 '22 18:09

Brian Bi