fetch_sub真的是原子的吗?

时间:2015-02-21 22:39:27

标签: 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::atomic_thread_fence(std::memory_order_acquire);
            deleteFromParent();
        }
    }

InternedString中的代码:

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()会为同一个对象多次调用。我不明白为什么......即使我过度释放,我仍然不会得到这种行为,我猜......

有人能帮助我吗?我做错了什么?

1 个答案:

答案 0 :(得分:0)

fetch_sub尽可能原子,但这不是问题。

尝试修改代码:

    if(m_refCount.fetch_sub(1, std::memory_order_release) == 1){
        Sleep(10);
        std::atomic_thread_fence(std::memory_order_acquire);
        deleteFromParent();

看看会发生什么。

如果你的析构函数被一个利用你的InternedString运算符的线程抢占了,他们会愉快地在不知不觉中获得对删除边缘的对象的引用。
这意味着您的其余代码可以自由引用已删除的对象,从而导致各种UB,包括可能重新递增您的完美原子引用计数,从而导致多次完全原子破坏。

假设任何人都可以在没有锁定析构函数的情况下复制引用,那么首先是完全错误的,只有当你把它埋在教科书中时才会变得更糟,这些操作符需要隐藏最终用户的参考玩法。

如果任何任务可以随时删除您的对象,那么像InternedString a = b;这样的一些代码将无法知道b是否是有效对象。
只有在对象确实有效时设置了所有引用时,引用计数机制才会按预期工作 您可以做的是在代码段中创建尽可能多的InternedString s ,而不能并行删除(无论是在初始化期间还是通过普通的互斥锁定),但是一旦析构函数出现,松散的,这是它的参考杂耍。

在不使用互斥锁或其他同步对象的情况下完成该工作的唯一方法是添加一种机制来获取一个让用户知道该对象已被删除的引用。这是an example of how that could be done

现在,如果您尝试将其全部隐藏在五个运营商的规则之下,唯一剩下的解决方案是向您的valid添加某种InternedString属性,每个代码都会必须在尝试访问基础字符串之前进行检查。

这相当于将多任务处理问题转移到界面最终用户的桌面上,最终用户最终会使用互斥锁来防止其他代码从他脚下删除对象,或者只是修改代码,直到隐式同步明显地处理问题,在应用程序中种植如此多的滴答时间炸弹。

原子计数器和/或结构不能替代多任务同步。除了一些可以设计超智能算法的专家外,原子变量只是涂上大量语法糖的巨大缺陷。