使用std :: atomic的无模互斥模的安全增量
我需要一个以循环方式使用的线程安全的Buffer对象池。 我通常会在那里放置一个互斥体,以使增量和模数线程安全,但可以使用std :: atomic写入它吗? 这里有一个示例界面。 如果它使事情更容易,缓冲区的总数可以是2的幂。 下一个缓冲区索引永远不会在类之外访问。
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 ];
}
你可以声明mNextBufferIndex
为std::atomic_ullong
然后使用
return mBuffers[(mNextBufferIndex++) % mTotalBuffers];
增量将是原子的,并且在返回之前计算模数。
使用非常大的未签名将避免计数器换行时发生的问题。
据我所知,这里有硬件辅助的互锁操作。 一个这样的操作是增量。 由于模操作可以独立于增量操作,因此不需要使其更复杂。
std::atomic
会重载operator++
,我会认为它有原子保证。
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];
}
如果你想用模或任何其他复杂的算术来做它,你可以使用比较和交换版本。
比较和交换或比较和交换之间的想法是,你做了你的计算,当你想把值写回内存位置(共享或其他)时,只有当其他人没有修改值时才会成功(如果他们只是重试操作,忙 - 等待)。 这只需要一个可预测的编号方案,这通常是很有可能的。
Buffer* GetNextBuffer()
{
// Let's assume that we wanted to do this
auto n = (mNextBufferIndex % mTotalBuffers) ++;
mNextBufferIndex = n;
return mBuffers[n];
}
假设mNextBufferIndex
是一个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];
}
你可能会认为这更类似于乐观的并发控制,但是如果你将自己限制在一个狭义的原子定义中,那么你就不会完成任何事情。
撇开我在这里计算的完全废话是表明compare_exchange
操作是多么强大,以及如何使用它来制作任何算术原子。 问题是当你有多个相互依赖的计算时。 在这种情况下,您需要在很多恢复例程中编写代码。
尽管互锁操作本身并不是免费的,并且会驱逐处理器中的缓存行。
作为参考,我可以推荐Mike Acton关于增量问题的纸张幻灯片。
链接地址: http://www.djcxy.com/p/78221.html上一篇: safe increment with modulo without mutex using std::atomic
下一篇: Sharing information between threads using mutex vs messages