代码之家  ›  专栏  ›  技术社区  ›  Patrick Jeeves

在没有互斥锁的情况下重新计数时,如何避免竞争条件?

  •  3
  • Patrick Jeeves  · 技术社区  · 6 年前

    我试图在下面的代码中找出如何避免竞争条件,线程A获取数据块,然后线程B释放/删除它,然后线程A添加它。是否可以在没有互斥锁的情况下修复此问题?我认为可以用atomic\u thread\u fence解决这个问题,但我真的不知道它将如何应用于这种情况。

    #include <atomic>
    
    class Foo
    {
        std::atomic<Datablock*> datablock
    
    public:
        Datablock * get_datablock()
        {
            Datablock * datablock = m_datablock.load();
            if(datablock) datablock->AddRef();
            return datablock;
        }
    
        void set_datablock(Datablock* datablock)
        {
            datablock = m_datablock.exchange(datablock);
            if(datablock) datablock->Release();
        }
    };
    
    1 回复  |  直到 6 年前
        1
  •  2
  •   Peter Cordes Steve Bohrer    6 年前

    我认为可以用atomic\u thread\u fence解决这个问题

    atomic_thread_fence 仅当使用的内存排序弱于默认值时才有用 seq_cst (参见 Jeff Preshing's article about C++11 fences 有关围栏和内存排序的更多信息。杰夫·普雷辛的文章很好;当然,在你尝试进行无锁编程时,一定要阅读其中的大部分内容)。

    原子线围栏 只能限制当前线程的内存操作如何全局可见的重新排序。它本身并不等待其他线程中的某些内容。


    当您尝试添加引用时,请准备好发现它已降至零 . 即 AddRef() 如果您来得太晚,并且另一个线程已经开始销毁refcounted对象,则可能会失败。

    因此,AddRef的实现将执行以下操作

    bool AddRef() {
        int old_count = m_refcount;
    
        do {
            if (old_count <= 0) {
                // we were too late; refcount had already dropped to zero
                // so another thread is already destroying the data block
                return false;
            }
        }while( !m_refcount.compare_exchange_weak(old_count, old_count+1) );
    
        return true;
    }
    

    我们使用CAS循环作为条件 fetch_add 而不是 fetch\u添加 然后 联合国 如果旧值太低,则执行此操作。如果出现以下情况,则后者将需要额外的工作来避免比赛条件 线程立即递增。(第二个线程将看到和old\u count为1,并认为这是可以的。)你可以通过 Release 函数将refcount设置为一个大的负数 之前 开始销毁一个块,但这很容易验证,而且几乎总是在第一次尝试中成功的CAS几乎不会比实际的 fetch\u添加 . 与CAS相比,单独的原子负载几乎是免费的,尤其是在x86上。(您可以使用 memory_order_relaxed 使其在弱有序体系结构上几乎免费。)


    请注意,refcount不能是 delete 当参考计数为零时 . 如果你这样做了,一个线程 get_datablock 并且做到了 m_datablock.load() ,然后休眠,然后用 datablock->AddRef() 如果指向的内存在休眠时被另一个线程删除,则可能会出现segfault(或导致其他未定义的行为)。


    这个答案并不能解决问题 整体 (在仍然允许 exchange 在您的 set_datablock 应用程序编程接口。我不确定API设计是否真的有效。

    这也不是一个完整的工作 atomic_shared_pointer 实施

    如果你想知道它是如何工作的,看看它的文档,或者希望有人写了一篇关于它是如何实现的帖子。它的开源库实现已经存在,但可能很难阅读。