Mike Vine
Mike Vine

Reputation: 9837

Atomically accessing a variable which isn't a `std::atomic`

We have some old code which we're updating to use more modern c++. It previously relied on the (yucky) visual studio extension for volatile to access a variable atomically.

The function is something like

T ReadAq(T* val)
{
    return *(volatile T*)val;
}

Note that the T is suitably aligned and small enough for the architectures we support to be natively able to a single read without ripping.

There's similar functionality for writing to the variable and all uses of the variable go through one of those functions.

And I don't really want to change its signature (as that would be a massive change to all callers) so I want to be able to do something like:

T ReadAq(T* val)
{
    return std::atomic_read(val, std::memory_order_acquire);
}

But it seems like functionality like this doesn't exist in the standard - all atomic operations are on std::atomic types. Any ideas on whether its possible to fix this without changing the signature of ReadAq?

Upvotes: 5

Views: 642

Answers (2)

Mike Vine
Mike Vine

Reputation: 9837

It seems like this is indeed a gap in the language and there's a Proposal to fix it for c++ 20:

p0019R7: Atomic Ref

Abstract: Extension to the atomic operations library to allow atomic operations to apply to non-atomic objects

An atomic reference is used to perform atomic operations on a referenced non-atomic object. The intent is for atomic reference to provide the best-performing implementation of atomic operations for the non-atomic object type. All atomic operations performed through an atomic reference on a referenced non-atomic object are atomic with respect to any other atomic reference that references the same object, as defined by equality of pointers to that object. The intent is for atomic operations to directly update the referenced object. An atomic reference constructor may acquire a resource, such as a lock from a collection of address-sharded locks, to perform atomic operations. Such atomic reference objects are not lock free and not address free. When such a resource is necessary, subsequent copy and move constructors and assignment operators may reduce overhead by copying or moving the previously acquired resource as opposed to re-acquiring that resource.

Introducing concurrency within legacy codes may require replacing operations on existing non-atomic objects with atomic operations such that the non-atomic object cannot be replaced with an atomic object.

An object may be heavily used non-atomically in well-defined phases of an application. Forcing such objects to be exclusively atomic would incur an unnecessary performance penalty.

Upvotes: 3

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136256

You can do that with a careful cast:

template<class T>
T ReadAq(T* val) {
    using AT = std::atomic<T>;
    static_assert(sizeof(T) == sizeof(AT), "Incompatible layout.");
    static_assert(alignof(T) == alignof(AT), "Incompatible layout.");
    return reinterpret_cast<AT*>(val)->load(std::memory_order_acquire);
}

Upvotes: 2

Related Questions