...

/

Multithreaded Programming with shared_ptr and Atomic References

Multithreaded Programming with shared_ptr and Atomic References

Learn safe and efficient multithreaded programming using shared_ptr and atomic references with an example.

Using shared_ptr in a multithreaded environment

What about the std::shared_ptr? Can it be used in a multithreaded environment, and how is reference counting handled when multiple threads are accessing an object referenced by multiple shared pointers?

To understand shared pointers and thread safety, we need to recall how std::shared_ptr is typically implemented. Consider the following code:

Press + to interact
// Thread 1
auto p1 = std::make_shared<int>(42);

The code creates an int on the heap and a reference-counted smart pointer pointing at the int object. When creating the shared pointer with std::make_shared(), a control block is created next to the int. The control block contains, among other things, a variable for the reference count, which is incremented whenever a new pointer to the int is created and decremented whenever a pointer to the int is destroyed. To summarize, when the preceding code line is executed, three separate entities are created:

  • The actual std::shared_ptr object p1 (local variable on the stack)
  • A control block (heap object)
  • An int (heap object)

The following figure shows the three objects:

Press + to interact
A shared_ptr instance, p1, that points to the integer object and a control block that contains the reference counting. In this case, there is only one shared pointer using the int, and hence the ref count is 1
A shared_ptr instance, p1, that points to the integer object and a control block that contains the reference counting. In this case, there is only one shared pointer using the int, and hence the ref count is 1

Now, consider what would happen if the following code was executed by a second thread:

Press + to interact
// Thread 2
auto p2 = p1;

We are creating a new pointer pointing at the int (and the control block). When creating the p2 pointer, we read p1, but we also need to mutate the control block when updating the reference counter. The control block lives on the heap and is shared among the two threads, so it needs synchronization to avoid data races. Since the control block is an implementation detail hidden behind ...