使用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 ];
    }

你可以声明mNextBufferIndexstd::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