提升原子引用计数示例是否包含错误?
Does boost atomic reference counting example contain a bug?
我指的是 this example。
作者使用 memory_order_release
来递减计数器。他们甚至在 the discussion section 中声明使用 memory_order_acq_rel
代替会过度。但是理论上下面的场景不会导致 x
永远不会被删除吗?
- 我们在不同的 CPU 上有两个线程
- 他们每个人都拥有一个共享指针的实例,两个指针共享同一控制块的所有权,不存在引用该块的其他指针
- 每个线程在其缓存中都有计数器,并且它们的计数器均为 2
- 第一个线程销毁它的指针,现在这个线程中的计数器是1
- 第二个线程销毁了它的指针,但是来自第一个线程的缓存无效信号可能仍在排队,因此它从自己的缓存中递减值并得到 1
- 两个线程都没有删除
x
,但是没有共享指针共享我们的控制块=>内存泄漏
来自 link 的代码示例:
#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>
class X {
public:
typedef boost::intrusive_ptr<X> pointer;
X() : refcount_(0) {}
private:
mutable boost::atomic<int> refcount_;
friend void intrusive_ptr_add_ref(const X * x)
{
x->refcount_.fetch_add(1, boost::memory_order_relaxed);
}
friend void intrusive_ptr_release(const X * x)
{
if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete x;
}
}
};
引自讨论部分:
It would be possible to use memory_order_acq_rel for the fetch_sub
operation, but this results in unneeded "acquire" operations when the
reference counter does not yet reach zero and may impose a performance
penalty.
单个原子变量的所有修改都按照全局修改顺序进行。两个线程不可能不同意这个顺序。
fetch_sub
操作是一个原子read-modify-write操作,要求总是从修改顺序中的同一个操作读取修改前的原子变量的值。
所以当第一个线程的 fetch_sub
在修改顺序中排在第一位时,第二个线程不可能读取 2
。如果硬件本身不支持这种原子访问,则实现必须确保不会发生这种缓存不一致,必要时借助锁。 (这就是原子的 is_lock_free
和 is_always_lock_free
成员要检查的内容。)
这完全独立于操作的内存顺序。这些只对访问 other 内存位置比原子变量本身重要。
我指的是 this example。
作者使用 memory_order_release
来递减计数器。他们甚至在 the discussion section 中声明使用 memory_order_acq_rel
代替会过度。但是理论上下面的场景不会导致 x
永远不会被删除吗?
- 我们在不同的 CPU 上有两个线程
- 他们每个人都拥有一个共享指针的实例,两个指针共享同一控制块的所有权,不存在引用该块的其他指针
- 每个线程在其缓存中都有计数器,并且它们的计数器均为 2
- 第一个线程销毁它的指针,现在这个线程中的计数器是1
- 第二个线程销毁了它的指针,但是来自第一个线程的缓存无效信号可能仍在排队,因此它从自己的缓存中递减值并得到 1
- 两个线程都没有删除
x
,但是没有共享指针共享我们的控制块=>内存泄漏
来自 link 的代码示例:
#include <boost/intrusive_ptr.hpp>
#include <boost/atomic.hpp>
class X {
public:
typedef boost::intrusive_ptr<X> pointer;
X() : refcount_(0) {}
private:
mutable boost::atomic<int> refcount_;
friend void intrusive_ptr_add_ref(const X * x)
{
x->refcount_.fetch_add(1, boost::memory_order_relaxed);
}
friend void intrusive_ptr_release(const X * x)
{
if (x->refcount_.fetch_sub(1, boost::memory_order_release) == 1) {
boost::atomic_thread_fence(boost::memory_order_acquire);
delete x;
}
}
};
引自讨论部分:
It would be possible to use memory_order_acq_rel for the fetch_sub operation, but this results in unneeded "acquire" operations when the reference counter does not yet reach zero and may impose a performance penalty.
单个原子变量的所有修改都按照全局修改顺序进行。两个线程不可能不同意这个顺序。
fetch_sub
操作是一个原子read-modify-write操作,要求总是从修改顺序中的同一个操作读取修改前的原子变量的值。
所以当第一个线程的 fetch_sub
在修改顺序中排在第一位时,第二个线程不可能读取 2
。如果硬件本身不支持这种原子访问,则实现必须确保不会发生这种缓存不一致,必要时借助锁。 (这就是原子的 is_lock_free
和 is_always_lock_free
成员要检查的内容。)
这完全独立于操作的内存顺序。这些只对访问 other 内存位置比原子变量本身重要。