2

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

Joald
  • 1,114
  • 10
  • 32
  • It's worth noting that if you create an `atomic` where T is not supported directly by the underlying hardware (which generally means that it's not a plain integer of the native platform size or smaller), essentially all it does is to wrap all the accessors with a mutex, so I'm not sure it will gain something over just duplicating the data and using a plain mutex. – Matteo Italia Dec 26 '17 at 11:08
  • 2
    Even when it works, on hardware not having 12 bytes atomic instructions, the implementation is likely to use some kind of mutex anyway. – Bo Persson Dec 26 '17 at 11:08
  • Btw, based on your description, I don't believe you need the strong reordering guarantee provided by the default [std::memory_order::seq_cst](http://en.cppreference.com/w/cpp/atomic/memory_order) that `atomic` uses. Since the variable is not guarding access to other memory or similar, I think that all you need is [std::memory_order_relaxed](http://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering) in this case. – Jesper Juhl Dec 26 '17 at 11:24
  • [Reading around](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65756) it does seem that you should link against `libatomic`; if that is missing from your system, that's the thing you should fix. But again, I wouldn't waste much time over `std::atomic` - it's important to access atomic primitives on types that support them, but for bigger stuff it's just a mutex. If even `std::mutex` turns out to be too heavyweight, you can implement a plain spinlock (given that you are probably going to access this thing very quickly). – Matteo Italia Dec 26 '17 at 11:26

1 Answers1

1

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

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299