Ignorant
Ignorant

Reputation: 2671

How to perform double buffering with atomic pointers?

Atomic newbie here. My code currently looks like this (simplified):

std::atomic<Object*> object;

void thread_a()
{
  object.load()->doSomething(); // (1)
}

void thread_b()
{
    Object* oldObject = object.load();
    Object* newObject = new Object();
    // Update new object accordingly...

    while (!object.compare_exchange_weak(oldObject, newObject));

    delete oldObject;
}

In words, my idea is to let thread_b atomically swap the shared object (double-buffering), while thread_a performs some work on it. My question: can I safely assume that the shared object will be "protected" against data races while thread_a calls doSomething() on it, as done in (1)?

Upvotes: 14

Views: 2522

Answers (2)

The Quantum Physicist
The Quantum Physicist

Reputation: 26256

I do this quite often, but... with shared pointers, and it's lock-free!

There's a problem with your design, as Some Programmer Dude suggested in his answer. But if you do this with shared_ptr, and your program logic allows it, you'll be fine.

The reason why this works with shared_ptr is that your object will not be deleted by force, as long as it lives somewhere else. So, this is how you do it:

std::shared_ptr<Object> object;

void thread_a()
{
  std::atomic_load(&object)->doSomething(); // (1)
}

void thread_b()
{
    std::shared_ptr<Object> oldObject = std::atomic_load(&object);
    std::shared_ptr<Object> newObject = std::make_shared<Object>();
    // Update new object accordingly...

    while (!std::atomic_compare_exchange_weak(object, oldObject, newObject));
}

EDIT: This atomic_load is specialized for the shared_ptr. This seems to have caused confusion in the comments: https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic

Upvotes: 4

Some programmer dude
Some programmer dude

Reputation: 409146

The fetching of the pointer with load() will be atomic, but the call to doSomething() itself will not be atomic.

That means the pointers could be swapped after load() is called but before doSomething() is called (which means doSomething() is called on the wrong and now deleted object).

Perhaps a mutex could be a better choice here?

Upvotes: 9

Related Questions