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