原子引用计数 [英] Atomic Reference Counting
问题描述
我试图确切地了解线程安全的原子引用计数是如何工作的,例如与 std::shared_ptr
一样.我的意思是,基本概念很简单,但我真的很困惑 decref 加 delete
如何避免竞争条件.
I'm trying to understand exactly how thread-safe, atomic reference counting works, for example as with std::shared_ptr
. I mean, the basic concept is simple, but I'm really confused about how the decref plus delete
avoids race conditions.
这个来自Boost的教程演示如何使用 Boost 原子库(或 C++11 原子库)实现原子线程安全引用计数系统.
This tutorial from Boost demonstrates how an atomic thread-safe reference counting system can be implemented using the Boost atomic library (or the C++11 atomic library).
#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;
}
}
};
好的,我大致了解了.但我不明白为什么以下场景是不可能的:
Okay, so I get the general idea. But I don't understand why the following scenario is NOT possible:
假设引用计数当前为 1
.
Say the refcount is currently 1
.
- 线程 A:原子地将引用计数减少到
0
. - 线程 B:将引用计数原子地增加到
1
. - 线程 A:在托管对象指针上调用
delete
. - 线程 B:将引用计数视为
1
,访问托管对象指针...SEGFAULT!
- Thread A: atomically decrefs the refcount to
0
. - Thread B: atomically increfs the refcount to
1
. - Thread A: calls
delete
on the managed object pointer. - Thread B: sees the refcount as
1
, accesses the managed object pointer... SEGFAULT!
我无法理解是什么阻止了这种情况的发生,因为在引用计数达到 0 和对象被删除之间的 之间没有什么可以阻止数据竞争.减少引用计数和调用 delete
是两个独立的非原子操作.那么没有锁怎么可能呢?
I can't understand what prevents this scenario from occurring, since there is nothing preventing a data race from between the time the refcount reaches 0, and the object is deleted. Decrefing the refcount and calling delete
are two separate, non-atomic operations. So how is this possible without a lock?
推荐答案
您可能高估了 shared_ptr 提供的线程安全性.
You probably overestimate the threadsafety a shared_ptr provides.
原子引用计数的本质是确保如果shared_ptr
(管理相同对象)的两个不同实例被访问/修改,将有没有竞争条件.但是,如果两个线程访问同一个 shared_ptr
对象(其中一个是写入),则 shared_ptr
并不能确保线程安全.一个例子是例如如果一个线程取消引用指针,而另一个线程重置它.
因此,关于 shared_ptr
唯一保证的是,只要在 shared_ptr 的单个实例上没有竞争(它也不访问对象),就不会有双重删除和泄漏它指向线程安全)
The essence of atomic ref counting is to ensure that if two different instances of a shared_ptr
(that are managing the same object) are accessed/modified, there will be no race condition. However, shared_ptr
doesn't ensure thread safety, if two threads access the same shared_ptr
object (and one of them is a write). One example would be e.g. if one thread dereferences the pointer, while the other resets it.
So about the only thing shared_ptr
gurantees is that there will be no double delete and no leak as long as there is no race on a single instance of a shared_ptr (It also doesn't make accesses to the object it points to threadsafe)
因此,如果没有其他线程可以同时删除/重置它,那么创建 shared_ptr 的副本也是安全的(您也可以说,它不是内部同步的).这是您描述的场景.
As a result, also creating a copy of a shared_ptr is only safe, if there is no other thread that could delete/reset it at the same time (you could also say, it is not internally synchronized). This is the scenario you describe.
再重复一遍:从多个线程访问单个 shared_ptr
实例,其中一个访问是对指针的写入仍然是竞争条件.
To repeat it once more: Accessing a single shared_ptr
instance from multiple threads where one of those accesses is a write to the pointer is still a race condition.
如果你想例如以线程安全的方式复制 std::shared_ptr
,您必须确保所有加载和存储都通过 std::atomic_...
专门用于 shared_ptr
的操作.
If you want to e.g. copy a std::shared_ptr
in a threadsafe manner, you have to ensure that all loads and stores happen via std::atomic_...
operations which are specialized for shared_ptr
.
这篇关于原子引用计数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!