mardi 15 février 2022

Synchronizing global reference-counted resource with atomics -- is relaxed appropriate here?

I have a global reference-counted object obj that I want to protect from data races by using atomic operations:

T* obj;                  // initially nullptr
std::atomic<int> count;  // initially zero

My understanding is that I need to use std::memory_order_release after I write to obj, so that the other threads will be aware of it being created:

void increment()
{
    if (count.load(std::memory_order_relaxed) == 0)
        obj = std::make_unique<T>();

    count.fetch_add(1, std::memory_order_release);
}

Likewise, I need to use std::memory_order_acquire when reading the counter, to ensure the thread has visibility of obj being changed:

void decrement()
{
    count.fetch_sub(1, std::memory_order_relaxed);

    if (count.load(std::memory_order_acquire) == 0)
        obj.reset();
}

I am not convinced that the code above is correct, but I'm not entirely sure why. I feel like after obj.reset() is called, there should be a std::memory_order_release operation to inform other threads about it. Is that correct?

Are there other things that can go wrong, or is my understanding of atomic operations in this case completely wrong?

Aucun commentaire:

Enregistrer un commentaire