如何实现不会溢出的原子参考计数器
How to implement atomic reference counter that does not overflow?
我正在考虑基于原子整数的参考计数,这些原子可以免受溢出的影响。如何做?
请不要关注这种溢出是否是现实的问题。即使不是实际重要的任务,任务本身也引起了我的兴趣。
示例
参考计数的示例实现在boost.atomic中显示为一个示例。基于该示例,我们可以提取以下示例代码:
struct T
{
boost::atomic<boost::uintmax_t> counter;
};
void add_reference(T* ptr)
{
ptr->counter.fetch_add(1, boost::memory_order_relaxed);
}
void release_reference(T* ptr)
{
if (ptr->counter.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete ptr;
}
}
此外,给出了解释
始终可以使用
memory_order_relaxed
来增加参考计数器:对对象的新引用只能从现有引用中形成,并且将现有参考从一个线程传递到另一个线程必须已经提供任何必需的同步。在一个线程中(通过现有引用)在删除其他线程中删除对象之前实施对对象的任何可能访问。这是通过删除参考后的"释放"操作来实现的(通过此引用显然必须发生对对象的任何访问),然后在删除对象之前进行"获取"操作。
可以将
memory_order_acq_rel
用于fetch_sub
操作,但是当参考计数器尚未达到零时,这会导致不需要的"获取"操作,并且可能施加性能罚款。
编辑>>>
看来BOOST.ATOMIC文档在这里可能是错误的。毕竟可能需要acq_rel
。
至少是使用std::atomic
完成boost::shared_ptr
的实现(还有其他实现)。请参阅文件boost/smart_ptr/detail/sp_counted_base_std_atomic.hpp
。
Herb Sutter还在他的演讲C 和2012年以后提到了这一点:Herb Sutter -Atomic&lt;>武器,第2个(共2个)(参考计数部分开始于1:19:51)。另外,他似乎不鼓励在这次演讲中使用围栏。
感谢用户2501在下面的评论中指出。
&lt;&lt;&lt;结束编辑
初始尝试
现在的问题是add_reference
如书面(某个时候)溢出。它会默默地这样做。显然,当调用匹配的release_reference
时,这可能会导致问题,以过早地破坏对象。(前提是然后再次调用add_reference
到达1
。)
我在想如何使add_reference
检测溢出并优雅地失败而不会冒险。
与0
进行比较,一旦我们离开fetch_add
,就不会做到,因为在两个其他线程之间可以再次调用add_reference
(到达1
),然后再调用release_reference
(错误地销毁生效的对象)。
首先检查(使用load
)也无济于事。这样,其他一些线程可以在我们对load
和fetch_add
的调用之间添加自己的引用。
这是解决方案吗?
然后我认为也许我们可以从load
开始,但是只有这样我们进行compare_exchange
。
因此,首先我们进行load
并获得本地值。如果是std::numeric_limits<boost::uintmax_t>::max()
,那么我们失败了。 add_reference
无法添加其他参考,因为已经采取了所有可能。
否则,我们将创建另一个本地值,即先前的本地参考计数加上1
。
现在,我们执行compare_exchange
作为预期值原始参考数(这确保在平均时间中没有其他线程修改的参考计数),并且作为所需值,逐渐增加的本地参考计数。
由于compare_exchange
可能会失败,因此我们必须在循环中执行此操作(包括load
)。直到检测到成功或最大值为止。
一些问题
- 这样的解决方案正确吗?
- 使其有效需要什么内存订购?
- 应该使用哪个
compare_exchange
?_weak
或_strong
? - 它会影响
release_reference
功能吗? - 它是在实践中使用的吗?
解决方案是正确的,也许可以用一件事对其进行改进。当前,如果该值在本地CPU中达到最大值,则可以通过另一个CPU将其降低,但是当前的CPU仍将缓存旧值。使用相同的expected
和newValue
进行虚拟compare_exchange
值得一试
其余的:
无论您使用_weak
还是_strong
,无论如何它都会在循环中运行,因此下一个load
将获得最新值。
对于add_reference
和release_reference
-然后谁将检查它是否真的添加了?会引发例外吗?如果是,它可能会起作用。但是通常,最好允许这样的低级别的事情不要失败,而是将UINTPTR_T用于参考计数器,因此它永远不会溢出,因为它足够大以覆盖地址空间,因此同时存在的任何数量的对象。p>否,由于上述原因而在实践中不使用它。
快速数学:说uint是32位,所以Max Uint是4G(4十亿美元)。每个参考/指针至少为4个字节(如果您在64位系统上),那么要溢出,您需要16Gbytes的存储器,用于存储指向同一对象的引用,这应该指向一个严重的Deign缺陷。
我会说这不是今天的问题,也不是在可预见的将来。
这个问题是没有问题的。即使假设原子增量需要1个CPU周期(不!),在4GHz CPU上,将需要半年的时间来包裹64位整数,提供CPU除了继续增量外别无其他。
。考虑到实际程序的现实,我很难相信这是一个真正的问题。
- 'short int'持有的值溢出,但"自动"不会溢出?
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 大于65535的C++数组[size]引发不一致的溢出
- 循环在计数器中不起作用
- 为什么我在leetcode上收到AddressSanitizer:地址0x602000000058上的堆缓冲区溢出错误
- C++中无符号字符溢出
- python集合的C++等价物是什么.计数器
- Python 集合.计数器,如何避免重复查找
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的 int main() 中出现堆栈溢出错误
- 整数溢出,最大值为 pow(10,19)
- 获取隐式转换溢出从无符号到已签名的警告
- 使用 strcat 获取缓冲区溢出错误
- LeetCode 1:两和 - 地址清理器:堆缓冲区溢出地址
- 请解释字谜的代码,我看不懂计数器数组,每个值已经是0
- 给定一个类型,如何派生一个泛型更广泛的类型(例如,用于溢出安全求和)?
- C++ 对象数组堆栈溢出
- 使用提升::lexical_cast捕获溢出
- 引用计数智能指针如何避免或处理引用计数器溢出?
- 如何实现不会溢出的原子参考计数器