简体   繁体   English

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

[英]How to avoid race condition when refcounting without a mutex?

I'm trying to figure out how to avoid the race condition in the following code, with thread A acquiring the datablock, then thread B Releasing/deleting it, and then thread A AddRefing it. 我试图弄清楚如何避免以下代码中的竞争条件,线程A获取数据块,然后线程B释放/删除它,然后线程A AddRefing它。 Is it possible to fix this without a mutex? 没有互斥锁可以解决这个问题吗? I think that it's possible to fix this with atomic_thread_fence, but I really have no idea how it would apply to this situation. 我认为可以用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来解决这个问题

atomic_thread_fence is only useful if you're using memory orderings weaker than the default seq_cst (see Jeff Preshing's article about C++11 fences for more about fences and memory ordering. Jeff Preshing's articles are excellent; definitely read most of those while you're trying to grok lockless programming). atomic_thread_fence仅在您使用比默认seq_cst弱的内存排序时才有用(请参阅Jeff Preshing关于C ++ 11围栏的文章,了解有关围栏和内存排序的更多信息.Jeff Preshing的文章非常出色;绝对是您阅读的大部分内容试图解锁无锁编程)。

atomic_thread_fence can only limit reordering of how the current thread's memory operations become globally visible. atomic_thread_fence只能限制当前线程的内存操作如何全局可见的重新排序。 It doesn't itself wait for something in other threads. 它本身并不等待其他线程中的某些东西。


When you try to add a reference, be prepared to find that it had already dropped to zero . 当您尝试添加引用时,请准备好发现它已经降为零 ie AddRef() can fail, if you were too late and another thread has already started to destroy the refcounted object. AddRef()可能会失败,如果你太晚了,另一个线程已经开始销毁refcounted对象。

So the implementation of AddRef would do something like 所以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;
}

We're using a CAS loop as a conditional fetch_add instead of doing a fetch_add and then un doing it if the old value was too low. 我们使用CAS环路有条件fetch_add而不是做一个fetch_add ,然后取消做,如果旧值太低。 The latter would require extra work to avoid a race condition if two threads were incrementing at once. 如果两个线程一次递增,后者将需要额外的工作来避免竞争条件。 (The 2nd thread would see and old_count of 1 and think it was ok.) You could maybe work around that by having the Release function set the refcount to a large negative number before starting to destroy a block, but this is easy to verify and a CAS that almost always succeeds on the first try is barely any slower than an actual fetch_add . (第二个线程会看到和old_count为1并且认为它没问题。)你可以通过让Release函数开始销毁块之前将refcount设置为一个大的负数来解决这个问题,但这很容易验证,在第一次尝试时几乎总是成功的CAS几乎不比实际的fetch_add慢。 The separate atomic load is nearly free compared to CAS, especially on x86. 与CAS相比,单独的原子负载几乎是免费的,特别是在x86上。 (You could use memory_order_relaxed for it to make it near-free on weakly-ordered architectures too.) (您可以使用memory_order_relaxed使其在弱有序的体系结构上几乎免费。)


Note that your refcount can't be part of the datablock that you delete when the refcount reaches zero . 请注意,当refcount达到零时,您的引用计数不能是您delete的数据块的一部分 If you did that, a thread that called get_datablock and did m_datablock.load() , then slept, then dereferenced that pointer with datablock->AddRef() could segfault (or cause other undefined behaviour) if the pointed-to memory was deleted by another thread while it was asleep. 如果你这样做,一个调用get_datablock并执行m_datablock.load()的线程然后睡眠,然后取消引用带有datablock->AddRef()指针可能会段错误(或导致其他未定义的行为)如果指向的内存被删除了在睡着的时候另一个线程。


This answer is doesn't solve the whole problem (of managing refcount blocks while still allowing an exchange in your set_datablock API. I'm not sure that API design really works. 这个答案并没有解决整个问题(管理refcount块,同时仍允许在set_datablock API中进行exchange 。我不确定API设计是否真的有效。

It's also not a complete working atomic_shared_pointer implementation. 它也不是一个完整的atomic_shared_pointer实现。

If you want to know how that works, look at its documents, or hopefully someone's written a post about how it's implemented. 如果您想知道它是如何工作的,请查看其文档,或希望有人写一篇关于它如何实现的文章。 Open-source library implementations of it exist, but are probably pretty hard to read. 它的开源库实现存在,但可能很难阅读。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM