C++ fetch_sub真的是原子的吗?
我有以下代码(用C++编写): StringRef类中的代码:C++ fetch_sub真的是原子的吗?,c++,multithreading,reference-counting,stdatomic,C++,Multithreading,Reference Counting,Stdatomic,我有以下代码(用C++编写): StringRef类中的代码: inline void retain() const { m_refCount.fetch_add(1, std::memory_order_relaxed); } inline void release() const { if(m_refCount.fetch_sub(1, std::memory_order_release) == 1){ std:
inline void retain() const {
m_refCount.fetch_add(1, std::memory_order_relaxed);
}
inline void release() const {
if(m_refCount.fetch_sub(1, std::memory_order_release) == 1){
std::atomic_thread_fence(std::memory_order_acquire);
deleteFromParent();
}
}
InternetString中的代码:
public:
inline InternedString(){
m_ref = nullptr;
}
inline InternedString(const InternedString& other){
m_ref = other.m_ref;
if(m_ref)
m_ref->retain();
}
inline InternedString(InternedString&& other){
m_ref = other.m_ref;
other.m_ref = nullptr;
}
inline InternedString& operator=(const InternedString& other){
if(&other == this)
return *this;
if(other.m_ref)
other.m_ref->retain();
if(m_ref)
m_ref->release();
m_ref = other.m_ref;
return *this;
}
inline InternedString& operator=(InternedString&& other){
if(&other == this)
return *this;
if(m_ref)
m_ref->release();
m_ref = other.m_ref;
other.m_ref = nullptr;
return *this;
}
/*! @group Destructors */
inline ~InternedString(){
if(m_ref)
m_ref->release();
}
private:
inline InternedString(const StringRef* ref){
assert(ref);
m_ref = ref;
m_ref->retain();
}
当我在多个线程中执行此代码时,会对同一对象多次调用deleteFromParent()。我不明白为什么。。。即使我过度释放,我也不应该有这种行为,我想
有人能帮我吗?我做错了什么?
fetch\u sub
是尽可能原子化的,但这不是问题所在
尝试修改代码,如下所示:
if(m_refCount.fetch_sub(1, std::memory_order_release) == 1){
Sleep(10);
std::atomic_thread_fence(std::memory_order_acquire);
deleteFromParent();
看看会发生什么
如果您的析构函数被使用InternedString
运算符的线程抢占,它们将愉快地在不知不觉中获得对即将删除的对象的引用。这意味着您的代码的其余部分可以自由引用已删除的对象,从而导致各种UBs,包括可能重新增加您的完美原子引用计数,从而导致多个完美原子破坏 假设任何人都可以在不首先锁定析构函数的情况下复制引用,这显然是错误的,而且如果你把它埋在教科书中,那么只会让它变得更糟,因为需要一大堆操作符来对最终用户隐藏引用变戏法 如果任何任务都可以随时删除您的对象,则需要一些代码,如
InternedString a=b
将无法知道b
是否为有效对象。只有在对象确实有效时设置了所有引用,引用计数机制才会按预期工作。
您可以做的是在代码段中创建任意数量的
InternedString
s,在代码段中不能并行删除(无论是在init期间还是通过普通的互斥锁),但一旦析构函数处于空闲状态,这就可以作为参考
在不使用互斥体或其他同步对象的情况下,使其工作的唯一方法是添加一种获取引用的机制,该机制将让用户知道对象已被删除。这是
现在,如果您试图将其全部隐藏在由五个运算符组成的规则下,唯一剩下的解决方案是在InternedString
中添加某种有效的属性,在尝试访问底层字符串之前,每一位代码都必须进行检查
这相当于将多任务问题抛到界面最终用户的桌面上,在最好的情况下,最终用户会使用互斥来防止其他代码位从他脚下删除对象,或者只是修补代码,直到隐式同步明显解决了问题,在应用程序中植入这么多滴答作响的定时炸弹
原子计数器和/或结构不能替代多任务同步。除了一些能够设计超智能算法的专家外,原子变量只是一个被大量语法糖衣包裹的巨大陷阱。我解决了这个问题。。。问题是我在复活一个濒临死亡的物体。由于删除和恢复由相同的旋转锁保护,因此发生的情况是对象被恢复,然后又被删除,因为它的引用计数达到零。我解决了这个问题,只需在锁定自旋锁后删除对象时测试ref计数是否为零。非常感谢。请注意旋转锁定可能会导致严重的性能损失。你可以考虑使用一个普通的互斥体来代替。如果您在运行测试程序时同时尝试这两种方法并查看性能表,您可能会注意到互斥锁导致的CPU消耗大大减少。当然,这完全取决于测试程序如何激发代码。