Customized lock-free RingBuffer I have implemented my own lock-free ringbuffer for multiple producers multiple consumers using vector. Can you guys help me review to see if there are any problems and ways to improve?

Explanation:
Each producer runs on its own thread and will use Push() to enter its item into the RingBuffer.

Each consumer runs on its own thread and has its own unique number. When consumer TryRead(), it will enter its unique number. 

RingBuffer will keep track of the last position each consumer read in std::vector&lt;long&gt; mReadIdxVec

If mReadIdxVec[consumer unique number] >= mMaxReadIdx, TryRead() will return a nullptr.

Question:
Within Push() method, will it be a problem if multiple producers on different threads call this line mVector[index] = pMsg; at the same time?

Can this code be improved further? Any comments are welcomed!



RingBuffer.h

#ifndef __RING_BUFFER_H_
#define __RING_BUFFER_H_

#include &lt;atomic&gt;

class RingBuffer
{
    private:
        std::vector&lt;Foo *&gt; mVector;
        std::atomic&lt;long&gt; mWriteIdx{0}; //mWriteIdx will not exceed 2,000,000,000
        std::atomic&lt;long&gt; mMaxReadIdx{0}; //mMaxReadIdx will not exceed 2,000,000,000
        std::vector&lt;long&gt; mReadIdxVec;

    public:
        RingBuffer(int pNumOfConsumers);
        void Push(Foo *);
        Foo * TryRead(const int&amp;);
};

#endif




RingBuffer.cc

#include "RingBuffer.h"
#define MAX_SIZE 1000

RingBuffer::RingBuffer(int pNumOfConsumers) 
{
    mVector.reserve(MAX_SIZE);
    mVector.assign(MAX_SIZE, nullptr);
    mReadIdxVec.assign(pNumOfConsumers, 0);
}

void RingBuffer::Push(Foo * pMsg)
{
    if(!pMsg) return;
    long writeIdx = mWriteIdx++;
    long index = writeIdx % MAX_SIZE;
    if(writeIdx &gt;= MAX_SIZE) delete mVector[index];
    mVector[index] = pMsg;
    mMaxReadIdx++;
}

Foo * RingBuffer::TryRead(const int&amp; pConsumer) 
{
    if(mReadIdxVec[pConsumer] &gt;= mMaxReadIdx) return nullptr;
    long index =  mReadIdxVec[pConsumer] % MAX_SIZE;
    mReadIdxVec[pConsumer]++;
    return mVector[index];
}
You have comments in the definition of RingBuffer on the value limits for some members, but there is nothing in the code that enforces that or checks to see if those limits are exceeded.

Why does TryRead take its parameter by const int&amp;?  The reference is unnecessary.  Just pass in the int.

Why is MAX_SIZE a macro?  It should be a constexpr (if your compiler supports it) or static const int if not.

In the RingBuffer constructor, you can combine the reserve and assign calls into one, and include it in the member initializer list.

Push will happily and silently overwrite things that have been added to your ring buffer but not consumed.

The way your Push is set up, you can have multiple threads write to the same memory location (if, for instance, there are enough other calls to Push so that the value calculated for index wraps back around).

It is also possible for mMaxReadIdx to indicate a slot is available for reading when it isn't.  For example, thread A starts a Push, and gets a write index.  Thread B then starts another Push, gets a write index, stores its value and increments mMaxReaddx before thread A has stored its message.  The slot used by thread A will be readable by a TryRead but won't have data in it yet.

TryRead can return the same message to multiple consumers, since each consumer has its own idea of where the bottom of the buffer is.

RingBuffer::RingBuffer(int pNumOfConsumers): mVector(MAX_SIZE, nullptr)