jeudi 21 février 2019

Wrapping an atomic type and ensuring that it remains atomic

I have a somewhat unique and interesting (and terrible) scenario that is forcing me to do some tricky stuff. The problem is as follows:

  1. We need an atomic type to do lock-free synchronization between a realtime thread and a background loading thread.
  2. The type (unfortunatley) must compile on:

    a. QNX which only has c++03, but supports boost::atomic.

    b. Nucleus which has c++11 (but cannot build boost::atomic, and the full std::atomic api is not available eg. atomic::compare_exchange_weak won't work)

This is forcing me to consider using both boost::atomic and std::atomic. They way I am approaching this is to generate a new type, that forwards all of the functionality to the relevant atomic type on the platform. The idea is something like this:


Atomic.hpp

namespace osal { namespace detail {

template <typename T, template <class> class TAtomic >
struct AtomicImpl {

    void store(T desired, memory_order order = osal::memory_order_seq_cst) {
        _atomic.store(desired, order);
    }

    // ... Other api

private:

    AtomicImpl& operator=(const AtomicImpl& rhs);
    // ... Other blocked operations
    TAtomic<T> _atomic;
};

}}


#if QNX
    #include <osal/QNX/Atomic.hpp>
#elif NUCLEUS
    #include <osal/NUCLEUS/Atomic.hpp>
#endif

This lays out a class which will forward to the atomic type with the correct API. Luckily the boost and std implementation match almost exactly.


Then in each of the individual OS files, something like this:

Nucleus/Atomic.hpp

#include <atomic>

namespace osal { 

template <typename T> struct atomic
{
    typedef detail::AtomicImpl<T, std::atomic> type;
};
}

This creates the atomic type with std::atomic allowing use like:

osal::atomic<uint8_t>::type a;
a.store(1);


In order to deal with the concept of memory order, there exists a similar system with a file to delegate to each of the OS implementations using the preprocessor. In the implementation, for eg. Nucleus, we have this:

Nucleus/MemoryOrder.hpp

#include <memory>
namespace osal {

using std::memory_order;
using std::memory_order_relaxed;
using std::memory_order_consume;
using std::memory_order_acquire;
using std::memory_order_release;
using std::memory_order_acq_rel;
using std::memory_order_seq_cst;

}

Obviously the equivalent using boost::... will be in the QNX file (lucky that they match again!).

The question

This appears to work. I can make an atomic and do operations on it. I have a concern:

Is this still atomic?

Now that we have introduced a indirection in the API, could there be problems with the order of the calls to the atomic object?

As a bonus question, given the constraints, is there a better way to do this?

Aucun commentaire:

Enregistrer un commentaire