![](/img/trans.png)
[英]Does std::atomic prevent reordering of nonatomic variables over the atomic variables
[英]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()
函數將始終拋出異常。
腳步:
如果效率是一個大問題,那么我建議不要對支票這么嚴格。 我猜測在正常情況下使用溢出不會是一個問題,但你真的需要完整的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值。
假設count
在std::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.