Reputation: 325
// Example program
#include <iostream>
#include <string>
#include <memory>
#include <atomic>
struct Smth {};
struct B { //Used in thread 2
B(std::shared_ptr<Smth>* smth) : smth_(smth) {};
~B();
void DoSomething() {
std::shared_ptr<Smth> newSmth = std::make_shared<Smth>();
smth_->swap(newSmth);
}
std::shared_ptr<Smth>* smth_;
};
struct A { //Used in thread 1&3
A() : smth_(std::make_shared<Smth>()), b_(&smth_) {};
~A();
std::shared_ptr<Smth> smth_;
B b_;
};
So is it good practice to pass pointer to shared_ptr so that i can swap the contents of shared_ptr in the separate thread? The second question is should I use the std::atomic_store to replace the shared_ptr::swap, if so how should i do that?
Upvotes: 2
Views: 751
Reputation: 65046
Contrary to (some) conventional wisdom, shared_ptr
objects are themselves not thread-safe. That is, you cannot concurrently manipulate1 a single shared_ptr
object on different threads, any more than you could concurrently manipulate a std::vector
or std::string
or any other standard library object with "default" thread-safety.
The confusion arises because a typical shared_ptr
does use atomic operations to manipulate its reference count - but that's only to support the scenario where different shared_ptr
objects point to the same underlying object (and hence share a reference count).
This kind of hidden sharing is the same type of thing that used to occur with COW implementations of std::string
: you could never safely manipulate the same std::string
objects on different threads without locking, but the COW behavior meant that without atomic operations you couldn't even manipulate two different std::string
objects which happened to share the same underlying buffer on different threads, a violation of the value-semantics of std::string
objects and the standard library thread safety guarantees. So in practice, you had atomic operations to manipulate the COW buffer reference count, but the other operations were written as usual in a not-thread-safe way.
So the bottom line is that not only is probably a bad design to share a shared_ptr
like this: it is explicitly not allowed since it is not thread-safe.
1 Concurrently manipulate here means to access the shared_ptr
objects on more than one thread, where at least some of the accesses are to non-const
methods. The standard library provides a general guarantee that concurrent access that consists only of const
-method access is safe (i.e., const
serves as a flag that the operation is "read-only" from a threading perspective).
Upvotes: 1
Reputation: 48645
My mind is boggling at how you might have ended up with this design choice... I hope it makes sense in your real project.
The only reason your should be passing a pointer (or preferably a reference) to a shared_ptr
is to re-seat it (as you are doing) and you don't need to synchronize the swap itself (shared_ptr
is already synchronized).
Swapping the contents, however, will cause synchronization issues with other threads that are using the object (reading or writing) at the time. So you would need to apply the same synchronization techniques you would apply if both threads were modifying the object.
This doesn't feel to me like a robust solution to whatever your actual problem is. Maybe consider passing a new std::shared_ptr
to the other thread and then signaling it so it knows to pick up the new shared pointer (by copy not pointer).
Upvotes: 1