Poco RefCountedObject 线程安全吗?
Is Poco RefCountedObject thread safe?
Poco RefCountedObject 提供 2 个接口:
inline void RefCountedObject::duplicate() const
{
++_counter;
}
inline void RefCountedObject::release() const throw()
{
try
{
if (--_counter == 0) delete this;
}
catch (...)
{
poco_unexpected();
}
}
与:
class Foundation_API RefCountedObject
/// A base class for objects that employ
/// reference counting based garbage collection.
///
/// Reference-counted objects inhibit construction
/// by copying and assignment.
{
public:
RefCountedObject();
/// Creates the RefCountedObject.
/// The initial reference count is one.
void duplicate() const;
/// Increments the object's reference count.
void release() const throw();
/// Decrements the object's reference count
/// and deletes the object if the count
/// reaches zero.
int referenceCount() const;
/// Returns the reference count.
protected:
virtual ~RefCountedObject();
/// Destroys the RefCountedObject.
private:
RefCountedObject(const RefCountedObject&);
RefCountedObject& operator = (const RefCountedObject&);
mutable AtomicCounter _counter;
};
注意:
mutable AtomicCounter _counter;
我的问题是如果我在多线程中使用 RefCountedObject 是否安全?
在我看来,这并不是因为只有 --_counter 是原子的,而是 if(--_count) 不是原子的,并且可能导致引用已删除的对象。
例如假设我有 2 个线程 A 和 B,一个执行复制,另一个执行释放,执行顺序如下:
- B开始执行释放并到达--_counter
- A开始执行duplicate并达到++_counter
- 此时_counter=1
- B 执行 --_counter 和 return if 计算的结果(即 0 && 计数器现在为 0)
- B被抢占并停止在分支语句(if)之前
- A 执行 _counter++ 和 return 对象的引用
- B 继续计算值为 0 的分支语句并删除对象
我们最终得到 A 对已删除对象的引用。
即使 mutable 关键字强制编译器不优化 _counter 它也无助于多线程
我错过了什么吗?
严格孤立地查看复制和发布,以上是正确的。
但是,如果您有两个线程,都具有指向同一个 RefCountedObject 的指针,则您应该在每个线程中有单独的 AutoPtr 实例(这意味着开始时 _counter > 1),或者,如果您共享相同的 AutoPtr,如果至少有一个线程可以更改 AutoPtr(这将导致调用 release()),则它必须由互斥锁保护。不用说在多个线程之间共享一个可变的 AutoPtr 是灾难的根源。
如果您通过手动调用duplicate() 和release() 来管理RefCountedObject(我不推荐这样做),您应该遵循POCO 的引用计数规则并非常小心。
因此,对于行为良好的代码,RefCountedObject 与 AutoPtr 一起用于多线程使用是安全的。
这是安全的,因为计数器基于 std::atomic
AND std::atomic
默认使用 std::memory_order_seq_cst
进行操作。
如果 std::atomic
使用 std::memory_order_relaxed
,那么实现将是 buggy/unsafe。
缺点是 std::memory_order_seq_cst
很“慢”。通过使用一些不同的代码和不同的内存排序命令 (https://en.cppreference.com/w/cpp/atomic/memory_order)
,有比 Poco 更快的实现,但不那么简单
Poco RefCountedObject 提供 2 个接口:
inline void RefCountedObject::duplicate() const
{
++_counter;
}
inline void RefCountedObject::release() const throw()
{
try
{
if (--_counter == 0) delete this;
}
catch (...)
{
poco_unexpected();
}
}
与:
class Foundation_API RefCountedObject
/// A base class for objects that employ
/// reference counting based garbage collection.
///
/// Reference-counted objects inhibit construction
/// by copying and assignment.
{
public:
RefCountedObject();
/// Creates the RefCountedObject.
/// The initial reference count is one.
void duplicate() const;
/// Increments the object's reference count.
void release() const throw();
/// Decrements the object's reference count
/// and deletes the object if the count
/// reaches zero.
int referenceCount() const;
/// Returns the reference count.
protected:
virtual ~RefCountedObject();
/// Destroys the RefCountedObject.
private:
RefCountedObject(const RefCountedObject&);
RefCountedObject& operator = (const RefCountedObject&);
mutable AtomicCounter _counter;
};
注意: mutable AtomicCounter _counter;
我的问题是如果我在多线程中使用 RefCountedObject 是否安全?
在我看来,这并不是因为只有 --_counter 是原子的,而是 if(--_count) 不是原子的,并且可能导致引用已删除的对象。 例如假设我有 2 个线程 A 和 B,一个执行复制,另一个执行释放,执行顺序如下:
- B开始执行释放并到达--_counter
- A开始执行duplicate并达到++_counter
- 此时_counter=1
- B 执行 --_counter 和 return if 计算的结果(即 0 && 计数器现在为 0)
- B被抢占并停止在分支语句(if)之前
- A 执行 _counter++ 和 return 对象的引用
- B 继续计算值为 0 的分支语句并删除对象
我们最终得到 A 对已删除对象的引用。 即使 mutable 关键字强制编译器不优化 _counter 它也无助于多线程
我错过了什么吗?
严格孤立地查看复制和发布,以上是正确的。
但是,如果您有两个线程,都具有指向同一个 RefCountedObject 的指针,则您应该在每个线程中有单独的 AutoPtr 实例(这意味着开始时 _counter > 1),或者,如果您共享相同的 AutoPtr,如果至少有一个线程可以更改 AutoPtr(这将导致调用 release()),则它必须由互斥锁保护。不用说在多个线程之间共享一个可变的 AutoPtr 是灾难的根源。
如果您通过手动调用duplicate() 和release() 来管理RefCountedObject(我不推荐这样做),您应该遵循POCO 的引用计数规则并非常小心。
因此,对于行为良好的代码,RefCountedObject 与 AutoPtr 一起用于多线程使用是安全的。
这是安全的,因为计数器基于 std::atomic
AND std::atomic
默认使用 std::memory_order_seq_cst
进行操作。
如果 std::atomic
使用 std::memory_order_relaxed
,那么实现将是 buggy/unsafe。
缺点是 std::memory_order_seq_cst
很“慢”。通过使用一些不同的代码和不同的内存排序命令 (https://en.cppreference.com/w/cpp/atomic/memory_order)