C++ minimal threadsafe array based on std::deque Here is a minimal example of a threadsafe array I want to build on for a timeseries application, with the following characteristics:


Ever-growing, and the already contained elements remain constant
(Usually) a single writer calling push_back
Multiple dependent readers


Here is the corresponding implementation, or rather an early attempt of it:

template&lt;typename T&gt;
struct threadsafe_array
{
    auto operator[](int i) const
    {
        return deq[i];
    }

    auto size() const
    {
        return deq_size.load(std::memory_order_acquire);
    }

    void push_back(T const&amp; t)
    {
        std::unique_lock&lt;std::mutex&gt; lock(mut);
        deq.push_back(t);
        lock.unlock();
        deq_size.fetch_add(1, std::memory_order_release);
    }

private:
    std::deque&lt;T&gt; deq;
    std::atomic&lt;int&gt; deq_size{0};
    std::mutex mut;
};


My underlying ideas:


reads of the available elements through operator[](int) are carried out lock-free.
a std::deque is used as the underlying container because it does not invalidate concurrent reads when doing a push-back (--in contrast, a std::vector could as it potentially does a reallocation)
the push_back is forwarded to the underlying deque, on which it is applied in an atomic way through locking the std::mutex. Thereafter, the variable deq_size of type std::atomic&lt;int&gt; variable is adjusted using release semantics (so that the previous push_back is not reordered after the fetch_add).
if there are reads occurring in between adding an element to the deque and the adjustment of the size, they have have to get along with a smaller size(), i.e. as if the array had not been updated. Calling operator[size()] therefore does not need to be undefined behvaiour as it is for std::deque (but that's more an inconsistency than a feature).


Questions:


Is this thing already threadsafe and doing what I wrote, or am I missing some points?
Are the memory orders in the atomic operations ok, or are there better choices (e.g. memory_order_relaxed for the load in size())?
Is it preferable doing the update of the size in push_back() under the lock (and thus, if I see it right, limit the size difference between size() and the underlying std::deque::size() to only one)?
Your code isn't thread-safe, as executing a read with .operator[]() and a write with .push_back() isn't synchronized in any way.
Even though no references to elements are invalidated by std::deque&lt;T&gt;::push_back(), it can change the data-structure to retrieve those references.

What you want to look into is called a readers-writer-lock, which is the primitive for allowing either multiple readers or only a single writer access at the same time.
C++17 provides it as std::shared_mutex, so you might have to use boost::shared_mutex unless your library is already there.