簡體   English   中英

使用std :: atomic而不使用互斥鎖的模數的C ++線程安全增量

[英]C++ thread-safe increment with modulo without mutex using std::atomic

我需要一個以循環方式使用的線程安全的Buffer-objects池。 我通常會在那里放一個互斥量來使增量和模數線程安全,但是可以用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;
};

選擇后可以安全地使用Modulo

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

假設mNextBufferIndexstd::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關於增量問題紙質幻燈片

當您有兩個輸入時,它是不可能的。 你可以用原子做的就是使用CPU支持的原子指令(我還沒有聽到可以做增量加模數作為操作的芯片),或者你可以先做計算而不是設置輸入所提供的值不要改變 - 但你的功能確實有兩個輸入,所以這也不起作用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM