Joald
Joald

Reputation: 1134

std::atomic on a simple POD class

I have read many articles and questions on that, such as this, this, and this, and all the answers provided are inconclusive as to what should actually be done in such a situation:

Currently I am writing a concurrent program where I need to concurrently access the size and top of a std::priority_queue<std::pair<int, int>> many times, while pushing and popping elements elsewhere.
But it would be too inefficient to lock the mutex every time a read is needed (and a solution to readers and writers problem is not efficient enough either).

The only thing that is good enough is to wrap those three attributes in a std::atomic and update the atomic on every update of the queue, which would work, if only the compiler allowed me to do that.

Sadly, g++ 7.2.0 outputs the

"undefined reference to '__atomic_load'"

error message while linking.

I tried adding -latomic to the CMakeLists.txt, but I got

"/usr/bin/ld: cannot find -latomic"

error instead (and I am not allowed to change or update the compiler).

My struct is a POD type (I checked that with static_assert), so I just don't get why does it not work. How can I get it to work?

EDIT: I compiled almost the same code as the one in the third link,

#include <iostream>
#include <atomic>

using namespace std;

struct Vec {
    int x, y, z;
};
int main() {
    std::atomic<Vec> x;
    Vec a;
    x = a;
}

and got the following error message

CMakeFiles/folder.dir/vec.cpp.o: In function `std::atomic<Vec>::store(Vec, std::memory_order)':
vec.cpp:(.text._ZNSt6atomicI3VecE5storeES0_St12memory_order[_ZNSt6atomicI3VecE5storeES0_St12memory_order]+0x47): undefined reference to `__atomic_store'

collect2: error: ld returned 1 exit status

Upvotes: 2

Views: 778

Answers (1)

Matteo Italia
Matteo Italia

Reputation: 126797

Moving/expaning from a comment:

g++ std::atomic<T> implementation requires libatomic for non-natively supported types.

I can reproduce and fix your error with -latomic indeed:

[matteo@teolapkubuntu /tmp]$ g++ -O3 test.cpp
/tmp/cc7YRyMy.o: In function `main':
test.cpp:(.text.startup+0x3f): undefined reference to `__atomic_store'
collect2: error: ld returned 1 exit status
[matteo@teolapkubuntu /tmp]$ g++ -O3 test.cpp -latomic
[matteo@teolapkubuntu /tmp]$ 

Still, if your toolchain doesn't provide libatomic I wouldn't worry too much.

Atomic stores for non-natively supported types in libatomic boil down to either a compare-exchange loop (see the LARGER macro), or, in the "degenerate" cases (T bigger than 16 bytes), to a plain mutex (libat_lock_n uses a hash of the target address and locks the corresponding locks from some global pool of locks).

This is all stuff that you can implement yourself without much hassle - actually, you are in a better position than the writers of libatomic, as you can add extra data members instead of having to exploit the target data itself or its address from a lock pool.

First of all, I'd try with a plain mutex protecting your separate copy of the data. Given that this mutex should be taken for an extremely short time, I don't expect much contention; given that you have one writer and multiple readers, you may experiment with std::shared_mutex if your compiler is recent enough.

If the plain mutex turns out to be too slow, you can fall back to a spinlock; again, since it should be taken for an extremely short time, it should be perfectly fine for your use case. If you need even more speed, you'll have to get your hands dirty with extra tricks (possibly using optimized RW spinlocks).

Upvotes: 1

Related Questions