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

标签 c++ atomic lock-free

我试图找出如何避免以下代码中的竞争条件,线程 A 获取数据 block ,然后线程 B 释放/删除它,然后线程 A AddRefing 它。是否可以在没有互斥锁的情况下解决这个问题?我认为可以用 atomic_thread_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();
    }
};

最佳答案

I think that it's possible to fix this with atomic_thread_fence

atomic_thread_fence 仅在您使用比默认 seq_cst 弱的内存排序时才有用(有关栅栏和内存排序的更多信息,请参阅 Jeff Preshing's article about C++11 fences。Jeff Preshing 的文章是非常好;在尝试理解无锁编程时一定要阅读其中的大部分内容)。

atomic_thread_fence 只能限制当前线程的内存操作如何变得全局可见的重新排序。它本身并不等待其他线程中的某些东西。


当您尝试添加引用时,请做好发现它已降为零的准备。即 AddRef() 可能会失败,如果您为时已晚并且另一个线程已经开始销毁引用计数的对象。

所以 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_add 然后在旧值是 un时执行太低。如果 两个 线程同时递增,后者将需要额外的工作来避免竞争条件。 (第二个线程会看到 old_count 为 1 并认为它没问题。)您可以通过 Release 函数将 refcount 设置为一个大的负数之前 开始销毁一个 block ,但这很容易验证,而且几乎总是在第一次尝试时成功的 CAS 几乎不比实际的 fetch_add 慢。与 CAS 相比,单独的原子加载几乎是免费的,尤其是在 x86 上。 (您可以使用 memory_order_relaxed 使其在弱序架构上也接近自由。)


请注意,当引用计数达到零时,您的引用计数不能成为您删除的数据 block 的一部分。如果你这样做了,一个线程调用了 get_datablock 并执行了 m_datablock.load(),然后休眠,然后使用 datablock->AddRef()< 取消引用该指针 如果指向的内存在另一个线程休眠时被删除,则可能会出现段错误(或导致其他未定义的行为)。


这个答案没有解决整个问题(管理引用计数 block ,同时仍然允许在您的set_datablock API 中进行exchange。我我不确定 API 设计是否真的有效。

它也不是一个完整的工作 atomic_shared_pointer实现。

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

关于c++ - 在没有互斥锁的情况下重新计数时如何避免竞争条件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48968296/

相关文章:

java - 共享计数器未按预期在并发服务器上递增

C++ 无锁模板化对象池

c++ - 使用 C++ 中的 Luabind 在循环中创建的对象导致应用程序崩溃

c++ - android + pthread + c++ = SIGSEGV

objective-c - 在 Objective C 中使用原子属性 : Any side effects?

c++ - std 原子同步和非原子变量 : not a stale data?

multithreading - CPU 寄存器和缓存一致性

c++ - 奇怪的shared_ptr行为

c++ - 如何在 C++ 中重载 "operator %"

c++ - 从指针创建 Mat