safe increment with modulo without mutex using std::atomic

I need a thread-safe pool of Buffer-objects used in a round-robin fashion. I normally would just put a mutex in there to make the increment and modulo thread safe, but is it possible to write it using std::atomic? Here's a sample interface. If it makes things easier the total number of buffers can be a power of two. The next buffer index is never accessed outside of the class.

class Buffer;

class BufferManager
{
public:

    BufferManager( size_t totalBuffers = 8 ) : mNextBufferIndex( 0 ), mTotalBuffers( totalBuffers )
    {
        mBuffers = new Buffer*[mTotalBuffers];
    }

    Buffer* GetNextBuffer()
    {
        // How to make this operation atomic?
        size_t index = mNextBufferIndex; 

        mNextBufferIndex = ( mNextBufferIndex + 1 ) % mTotalBuffers;

        return mBuffers[index];
    }

private:
    Buffer**            mBuffers;
    size_t              mNextBufferIndex;
    size_t              mTotalBuffers;
};

模数可以在选择后安全使用

std::atomic<size_t> mNextBufferIndex;

    Buffer* GetNextBuffer()
    {
       // How to make this operation atomic?
       size_t index = mNextBufferIndex ++; 

       size_t id = index % mTotalBuffers;

       // If size could wrap, then re-write the modulo value.
       // oldValue keeps getting re-read.  
       // modulo occurs when nothing else updates it.
       size_t oldValue =mNextBufferIndex;
       size_t newValue = oldValue % mTotalBuffers;
       while (!m_mNextBufferIndex.compare_exchange_weak( oldValue, newValue, std::memory_order_relaxed ) )
            newValue = oldValue % mTotalBuffers; 
       return mBuffers[id ];
    }

You could just declare the mNextBufferIndex an std::atomic_ullong and then use

return mBuffers[(mNextBufferIndex++) % mTotalBuffers];

The increment will be atomic and you compute the modulo just before returning.

Using a very large unsigned will avoid problems that would occur when the counter wraps.


To my knowledge, there's hardware assisted interlocked operations for this. One such operation is increment. You don't need to make it more complicated since the modulo operation can act independently from the increment operation.

std::atomic does overload the operator++ and I would think that it has the atomic guarantee.

Buffer* GetNextBuffer()
{
   // Once this inc operation has run the return value
   // is unique and local to this thread the modulo operation
   // does not factor into the consistency model 
   auto n = mNextBufferIndex++; 
   auto pool_index = n % mTotalBuffers;
   return mBuffers[pool_index];
}

If you wanted to do it with the modulo or any other complex arithmetic you use a compare and exchange version.

The idea between compare and exchange or compare and swap is that you do your computation and when you want to write the value back to the memory location (shared or otherwise) it will only succeed if no one else did not modify the value in the meantime (if they do you just retry the operation, busy-wait). This requires only a predictable numbering scheme which is often very possible to do.

Buffer* GetNextBuffer()
{
   // Let's assume that we wanted to do this
   auto n = (mNextBufferIndex % mTotalBuffers) ++; 
   mNextBufferIndex = n;
   return mBuffers[n];
}

Assuming mNextBufferIndex is an std::atomic .

Buffer* GetNextBuffer()
{
   // Let's assume that we wanted to do this
   auto n = (mNextBufferIndex % mTotalBuffers)++;
   // This will now either succeed or not in the presence of concurrency
   while (!std::compare_exchange_weak(mNextBufferIndex, n)) {
     n = (mNextBufferIndex % mTotalBuffers)++;
   }
   return mBuffers[n];
}

You may think that this is more akin to optimistic concurrency control but if you limit your self to a definition of what is atomic that is too narrow you won't get anything done.

Putting aside that what I'm computing here is complete nonsense is shows how powerful the the compare_exchange operation is and how to use it to make any arithmetic atomic. The problem is really when you have multiple computations that depend on each other. You need to code in a lot of recovery routines when this is the case.

Though, interlocked operations by themselves are not free and evict cache lines in the processor.

For reference I can recommend Mike Acton's paper slides about the increment problem .

链接地址: http://www.djcxy.com/p/78222.html

上一篇: C ++ 11:确实是atomic :: compare

下一篇: 使用std :: atomic的无模互斥模的安全增量