簡體   English   中英

防止std :: atomic溢出

[英]prevent std::atomic from overflowing

我有一個原子計數器( std::atomic<uint32_t> count ),它按順序遞增值到多個線程。

uint32_t my_val = ++count;

在我得到my_val之前,我想確保增量不會溢出(即:返回0)

if (count == std::numeric_limits<uint32_t>::max())
    throw std::runtime_error("count overflow");

我認為這是一個天真的檢查,因為如果在增加計數器之前由兩個線程執行檢查,則增加的第二個線程將返回0

if (count == std::numeric_limits<uint32_t>::max()) // if 2 threads execute this
    throw std::runtime_error("count overflow");
uint32_t my_val = ++count;       // before either gets here - possible overflow

因此我想我需要使用CAS操作來確保當我增加計數器時,我確實防止了可能的溢出。

所以我的問題是:

  • 我的實施是否正確?
  • 它是否盡可能高效(特別是我需要檢查max兩次)?

我的代碼(帶有工作范例)如下:

#include <iostream>
#include <atomic>
#include <limits>
#include <stdexcept>
#include <thread>

std::atomic<uint16_t> count;

uint16_t get_val() // called by multiple threads
{
    uint16_t my_val;
    do
    {
        my_val = count;

        // make sure I get the next value

        if (count.compare_exchange_strong(my_val, my_val + 1))
        {
            // if I got the next value, make sure we don't overflow

            if (my_val == std::numeric_limits<uint16_t>::max())
            {
                count = std::numeric_limits<uint16_t>::max() - 1;
                throw std::runtime_error("count overflow");
            }
            break;
        }

        // if I didn't then check if there are still numbers available

        if (my_val == std::numeric_limits<uint16_t>::max())
        {
            count = std::numeric_limits<uint16_t>::max() - 1;
            throw std::runtime_error("count overflow");
        }

        // there are still numbers available, so try again
    }
    while (1);
    return my_val + 1;
}

void run()
try
{
    while (1)
    {
        if (get_val() == 0)
            exit(1);
    }

}
catch(const std::runtime_error& e)
{
    // overflow
}

int main()
{
    while (1)
    {
        count = 1;
        std::thread a(run);
        std::thread b(run);
        std::thread c(run);
        std::thread d(run);
        a.join();
        b.join();
        c.join();
        d.join();
        std::cout << ".";
    }
    return 0;
}

是的,您需要使用CAS操作。

std::atomic<uint16_t> g_count;

uint16_t get_next() {
   uint16_t new_val = 0;
   do {
      uint16_t cur_val = g_count;                                            // 1
      if (cur_val == std::numeric_limits<uint16_t>::max()) {                 // 2
          throw std::runtime_error("count overflow");
      }
      new_val = cur_val + 1;                                                 // 3
   } while(!std::atomic_compare_exchange_weak(&g_count, &cur_val, new_val)); // 4

   return new_val;
}

想法如下:一旦g_count == std::numeric_limits<uint16_t>::max()get_next()函數將始終拋出異常。

腳步:

  1. 獲取計數器的當前值
  2. 如果它是最大值,則拋出異常(不再提供數字)
  3. 獲取新值作為當前值的增量
  4. 嘗試以原子方式設置新值。 如果我們未能設置它(它已經由另一個線程完成),請再試一次。

如果效率是一個大問題,那么我建議不要對支票這么嚴格。 我猜測在正常情況下使用溢出不會是一個問題,但你真的需要完整的65K范圍(你的例子使用uint16)?

如果你假設你運行的線程數有一些最大值會更容易。 這是一個合理的限制,因為沒有程序具有無限數量的並發性。 因此,如果你有N線程,你可以簡單地將溢出限制減少到65K - N 要比較你是否溢出,你不需要CAS:

uint16_t current = count.load(std::memory_order_relaxed);
if( current >= (std::numeric_limits<uint16_t>::max() - num_threads - 1) )
    throw std::runtime_error("count overflow");
count.fetch_add(1,std::memory_order_relaxed);

這會產生軟溢出情況。 如果兩個線程同時到達它們,它們都可能通過,但這沒關系,因為count變量本身永遠不會溢出。 此時任何未來到達都將在邏輯上溢出(直到計數再次減少)。

在我看來,仍有一個競爭條件,其中count將暫時設置為0,以便另一個線程將看到0值。

假設countstd::numeric_limits<uint16_t>::max()並且兩個線程嘗試獲取遞增的值。 在線程1執行count.compare_exchange_strong(my_val, my_val + 1) ,count設置為0,這是線程2在線程1有機會恢復count之前調用並完成get_val()時將看到的內容到max()

暫無
暫無

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

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