【发布时间】:2016-06-28 13:53:56
【问题描述】:
我正在考虑基于原子整数的引用计数,这样可以避免溢出。怎么做?
请不要关注这种溢出是否是一个现实问题。这项任务本身引起了我的兴趣,即使实际上并不重要。
示例
引用计数的示例实现在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++ and Beyond 2012: Herb Sutter - atomic<> Weapons, 2 of 2 中也提到了这一点(引用计数部分从 1:19:51 开始)。此外,他似乎不鼓励在这次谈话中使用栅栏。
感谢user 2501 在下面的 cmets 中指出这一点。
初步尝试
现在的问题是 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功能吗? - 在实践中使用了吗?
【问题讨论】:
-
fetch_add返回原子的先前值。这还不足以检测溢出吗? -
nit - uint 不会溢出,它们会换行,可以检测到。
-
如果事后检测到溢出,您也可以
abort,不是吗?无论哪种方式,您都可能无法安全地继续执行程序。 -
Herb Sutter 提到你需要在大约 1 小时 22 分钟时为减量提供获取-释放语义:channel9.msdn.com/Shows/Going+Deep/…。
-
@AdamBadura 放松增量,acq_rel 减量。最重要的是,如果您不确定,请使用默认模式(您不必显式编写),即
memory_order_seq_cst。否则你只会打自己的脚。
标签: c++ multithreading atomic integer-overflow reference-counting