145

I'm reading http://gcc.gnu.org/onlinedocs/libstdc++/manual/shared_ptr.html and some thread safety issues are still not clear for me:

  1. Standard guarantees that reference counting is handled thread safe and it's platform independent, right?
  2. Similar issue - standard guarantees that only one thread (holding last reference) will call delete on shared object, right?
  3. shared_ptr does not guarantee any thread safety for object stored in it?

EDIT:

Pseudo code:

// Thread I
shared_ptr<A> a (new A (1));

// Thread II
shared_ptr<A> b (a);

// Thread III
shared_ptr<A> c (a);

// Thread IV
shared_ptr<A> d (a);

d.reset (new A (10));

Calling reset() in thread IV will delete previous instance of A class created in first thread and replace it with new instance? Moreover after calling reset() in IV thread other threads will see only newly created object?

Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
Goofy
  • 5,187
  • 5
  • 40
  • 56

3 Answers3

111

As others have pointed out, you've got it figured out correctly regarding your original 3 questions.

But the ending part of your edit

Calling reset() in thread IV will delete previous instance of A class created in first thread and replace it with new instance? Moreover after calling reset() in IV thread other threads will see only newly created object?

is incorrect. Only d will point to the new A(10), and a, b, and c will continue to point to the original A(1). This can be seen clearly in the following short example.

#include <memory>
#include <iostream>
using namespace std;

struct A
{
  int a;
  A(int a) : a(a) {}
};

int main(int argc, char **argv)
{
  shared_ptr<A> a(new A(1));
  shared_ptr<A> b(a), c(a), d(a);

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;

  d.reset(new A(10));

  cout << "a: " << a->a << "\tb: " << b->a
     << "\tc: " << c->a << "\td: " << d->a << endl;
                                                                                                                 
  return 0;                                                                                                          
}

(Clearly, I didn't bother with any threading: that doesn't factor into the shared_ptr::reset() behavior.)

The output of this code is

a: 1 b: 1 c: 1 d: 1

a: 1 b: 1 c: 1 d: 10

Community
  • 1
  • 1
Nicu Stiurca
  • 8,747
  • 8
  • 40
  • 48
47
  1. Correct, shared_ptrs use atomic increments/decrements of a reference count value.

  2. The standard guarantees only one thread will call the delete operator on a shared object. I am not sure if it specifically specifies the last thread that deletes its copy of the shared pointer will be the one that calls delete (likely in practice this would be the case).

  3. No they do not, the object stored in it can be simultaneously edited by multiple threads.

EDIT: Slight followup, if you want to get an idea of how shared pointers work in general you might want to look at the boost::shared_ptr source: https://www.boost.org/doc/libs/release/boost/smart_ptr/shared_ptr.hpp.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
NothingMore
  • 1,211
  • 9
  • 19
  • 3
    1. When you say "'shared_ptrs' use atomic increments/decrements of a reference count value." Do you mean they do not use any internal lock for atomic increment/decrement, which does the context switch? In simple language does multiple threads could increment/decrement reference count without using lock? An the atomic increment is done by special atomic_test_and_swap/atomic_test_and_increment instructions? – rahul.deshmukhpatil Aug 17 '15 at 08:16
  • @rahul the compiler is free to use a mutex/lock, but most good compilers will not use a mutex/lock on platforms where it can be done lock-free. – Bernard Feb 23 '17 at 10:30
  • @Bernard: do you mean it depends upon "compilers std lib shared_ptr" implementation for the platform? – rahul.deshmukhpatil Feb 27 '17 at 07:35
  • 3
    Yes. From my understanding, the standard does not say that it must be lock-free. But in the latest GCC and MSVC, it is lock-free on Intel x86 hardware, and I think other good compilers are likely to do the same when the hardware supports it. – Bernard Feb 27 '17 at 07:54
33

std::shared_ptr is not thread safe.

A shared pointer is a pair of two pointers, one to the object and one to a control block (holding the ref counter, links to weak pointers ...).

There can be multiple std::shared_ptr and whenever they access the control block to change the reference counter it's thread-safe but the std::shared_ptr itself is NOT thread-safe or atomic.

If you assign a new object to a std::shared_ptr while another thread uses it, it might end up with the new object pointer but still using a pointer to the control block of the old object => CRASH.

Laura
  • 8,100
  • 4
  • 40
  • 50
Lothar
  • 12,537
  • 6
  • 72
  • 121
  • 12
    We could say that single `std::shared_ptr` instance is not thread safe. From std::shared_ptr reference : `If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur;` – JKovalsky Jun 05 '18 at 10:48
  • 16
    This could be worded better. A `std::shared_ptr` instance is guaranteed thread-safe when always used **by value** (copied/moved) across thread boundaries. All other uses, `std::shared_ptr&` are unsafe across thread boundaries – WhiZTiM Jun 27 '20 at 08:44
  • I don't care how you name it. The main information i give here and that was missing in other answers is that a std::shared_ptr has multiple members. When knowing this everyone who does multithreading programming understand 100%. Using different names makes it IMHO more ambiguous – Lothar Mar 20 '21 at 23:45
  • 6
    This answer is very misleading and not clear at all. Yes, a single std::shared_ptr instance is not thread safe, but that's not the intended use case *nor* is is what is asked in the question. – Martin Ba Aug 26 '21 at 10:15
  • @MartinBa. Why is this not what is asked? I think it is exactly what is asked. And a reason whey atomic shared pointers are coming in C++23 and are well needed. – Lothar Sep 01 '21 at 08:05
  • 3
    It is not what's being asked, because the OP does not share the share_ptr object across threads, other than for init. Each thread in the OP has it's own shared_ptr object, and all questions AFAICT pertain to these per-thread shared_ptr objects (that point to a shared object). – Martin Ba Sep 01 '21 at 09:11
  • A `std::shared_ptr` can be made thread-safe if it is only used read-only once multiple threads are created. The effects of race conditions would also be nullified unless the object pointed to by the shared_ptr is also not thread-safe (e.g. a pointer member of struct). – Zenul_Abidin Feb 20 '22 at 03:39
  • What does "while another thread uses it" mean? Uses the pointed-to object, re-assigning the smart pointer itself, how the pointer was passed to that scope (by value, by reference)? – Vassilis Mar 10 '22 at 21:35
  • @Vassilis: They're referring to sharing a single variable (either by both of them referencing the same object directly, e.g. a global named `ptr`, or by one or both referencing the same object indirectly, e.g. through a `std::shared_ptr&`). Basically, `auto a = std::make_shared(1);` then passing `a` by value to threads keeps things safe (control block-wise), because the control block is accessed through independent `shared_ptr` objects, but if you mutate `a` itself in two different threads, it's just as racy as mutating an `int a;` in two different threads. – ShadowRanger Nov 08 '22 at 18:35
  • This answer would be better off if it was not posted at all. It is completely misleading both to OP trying to understand shared_ptr and to the people searching for "is shared_ptr threadsafe". – Shameel Mohamed Feb 11 '23 at 16:14
  • @ShameelMohamed why? It's the only exact answer that tells you what is going on without going into abstracted type kung fu. – Lothar Feb 13 '23 at 21:53