Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Acquire/release semantics on atomic variables accessed via []-operator

Assume an array of atomic variables and a class that regulates access to that array by overloading the class' []-operator to return a reference to the atomic variable at position idx:

class MyClass {
public:
    MyClass()
    {
        //initalize every array element with nullptr
        for (auto& it : array) {
            it = nullptr;
        }
    }
    std::atomic<int*>& operator[](const size_t idx)
    {
         //there is some more code here, doing basic safety checks,...
         return array[idx];
    }
private:
    std::array<std::atomic<int*>, 1000> array;

}

We can access elements of arraylike this:

MyClass foo();
int *a = foo[0];
int b = 3;
foo[1] = &b

Note that any access to such an element will by default be done using memory_order_seq_cst. To change the enforced memory order, one could do:

int *a = foo[0].load(memory_order_acquire);
foo[1].store(&b, memory_order_release);

But how can I change implementation of the []-operator such that memory_order_acquire is used for all reads and memory_order_release is used for all writes? The reason why I want to do this in the definition of the []-operator is that there are a lot of accesses the elements of array at a lot of different locations in the source and I do not want to spread the used memory ordering to all of them.

EDIT: As discussed in the comments, one could replace the []-operator with a getter and setter. However, that would require all accesses to be replaced by the appropriate function; plus I am interested whether or not it is possible to do it the way I outlined above.

like image 761
mort Avatar asked Aug 01 '14 10:08

mort


1 Answers

You can use an intermediate reference object that is the result of the operator[]. This object then applies the load or store operation based on how the object is used in a future expression.

class MyClass {
    struct Ref {
        std::atomic<int *> &ref_;
        Ref (std::atomic<int *> &r) : ref_(r) {}
        operator int * () const {
            return ref_.load(std::memory_order_acquire);
        }
        int * operator = (int *ptr) const {
            ref_.store(ptr, std::memory_order_release);
            return ptr;
        }
    };
public:
    //...
    Ref operator[](const size_t idx)
    {
         //there is some more code here, doing basic safety checks,...
         return array[idx];
    }
    //...
};

Then, the conversion operator or the assignment operator will use the right memory order constraint:

MyClass foo;
int *a = foo[0];  // uses conversion operator, load(acquire)
int b = 3;
foo[1] = &b;      // uses assignment operator, store(release)
like image 78
jxh Avatar answered Sep 19 '22 01:09

jxh