防止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()
函数将总是抛出异常。
步骤:
- 获取计数器的当前值
- 如果是最大值,抛出异常(没有可用的数字了)
- 获取新值作为当前值的增量 尝试自动设置新值。如果我们设置失败(它已经被另一个线程完成了),再试一次。
如果效率是一个大问题,那么我建议不要对检查这么严格。我猜在正常使用下溢出不会是一个问题,但你真的需要完整的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)
的那一刻,计数被设置为0,这就是线程2在线程1有机会将count
恢复为max()
之前是否恰好调用并完成get_val()
。
- 如何从 std::atomic 中提取指针 T<T>?
- 'short int'持有的值溢出,但"自动"不会溢出?
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 大于65535的C++数组[size]引发不一致的溢出
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 为什么我在leetcode上收到AddressSanitizer:地址0x602000000058上的堆缓冲区溢出错误
- C++中无符号字符溢出
- std::memory_order for std::atomic:<T>:wait
- MESI协议和std::atomic-它是否确保所有写入立即对其他线程可见?
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的 int main() 中出现堆栈溢出错误
- 整数溢出,最大值为 pow(10,19)
- 获取隐式转换溢出从无符号到已签名的警告
- 使用 strcat 获取缓冲区溢出错误
- LeetCode 1:两和 - 地址清理器:堆缓冲区溢出地址
- 给定一个类型,如何派生一个泛型更广泛的类型(例如,用于溢出安全求和)?
- C++ 对象数组堆栈溢出
- 使用提升::lexical_cast捕获溢出
- C++ Unordered_set功能中的溢出
- 防止std::atomic溢出