Consider the following class with a shared_ptr data object. The purpose is to obtain cheaply copyable objects with a data cache shared among the copies.
#include <memory> // std::shared_ptr
#include <mutex> // std::mutex, std::lock_guard
class DataClass {
std::shared_ptr<DataType> data;
std::shared_ptr<std::mutex> data_lock;
public:
DataClass() : data(std::make_shared<DataType>()), data_lock(std::make_shared<std::mutex>()) {}
DataType get_data() const {
std::lock_guard<std::mutex> lock_guard(*data_lock);
if (/* data not cached */) {
/* calculate and store data */
}
return *data;
}
};
Are calls to get_data() thread-safe? If not, how can that be achieved?
I do have code that seems to end up in a deadlock (far too large/complicated to post), which relies on the above to work fine in parallel calculations.
Are calls to get_data() thread-safe?
As long as data_lock is not modified after the initialization in the DataClass constructor, it is thread-safe. shared_ptr itself is not thread-safe beyond reference counting, meaning that if the pointer itself is modified, it must be protected with a mutex. If it remains constant (which you can ensure by marking data_lock with const) then multiple threads can read the pointer concurrently, including copying the pointer and incrementing/decrementing the reference counter.
If not, how can that be achieved?
If you do have an instance where data_lock can be modified, you must protect it with a mutex lock or otherwise guarantee that the modification cannot happen while other threads are accessing the pointer. In this particular case this would probably mean to get rid of data_lock pointer and replace it with a mutex.
You should also take care to not allow the pointed object to be accessed after data_lock is modified. Remember that dereferencing shared_ptr returns a raw reference without incrementing the reference counter. If that shared_ptr is modified, the object it previously pointed to can be destroyed and the raw references become dangling. This can happen in a subtle way, e.g. a reference to the mutex may be saved in a lock_guard, so after you modify data_lock under the lock the lock_guard destructor will attempt to unlock an already destroyed mutex.
Related
As it's well known that shared_ptr only guarantees access to underlying control block is thread
safe and no guarantee made for accesses to owned object.
Then why there is a race condition in the code snippet below:
std::shared_ptr<int> g_s = std::make_shared<int>(1);
void f1()
{
std::shared_ptr<int>l_s1 = g_s; // read g_s
}
void f2()
{
std::shared_ptr<int> l_s2 = std::make_shared<int>(3);
std::thread th(f1);
th.detach();
g_s = l_s2; // write g_s
}
In the code snippet above, the owned object of the shared pointer named g_s are not accessed indeed.
I am really confused now. Could somebody shed some light on this matter?
std::shared_ptr<T> guarantees that access to its control block is thread-safe, but not access to the std::shared_ptr<T> instance itself, which is generally an object with two data members: the raw pointer (the one returned by get()) and the pointer to the control block.
In your code, the same std::shared_ptr<int> instance may be concurrently accessed by the two threads; f1 reads, and f2 writes.
If the two threads were accessing two different shared_ptr instances that shared ownership of the same object, there would be no data race. The two instances would have the same control block, but accesses to the control block would be appropriately synchronized by the library implementation.
If you need concurrent, race-free access to a single std::shared_ptr<T> instance from multiple threads, you can use std::atomic<std::shared_ptr<T>>. (There is also an older interface that can be used prior to C++20, which is deprecated in C++20.)
Is there a thread-safe reference counter class in the standard C++ library, (or as an extension in Visual Studio), or would I need to write this kind of object from scratch?
I'm hoping for an object that purely performs reference counting as shared_ptr might, with the exception that it does so across multiple threads accurately, and without managing anything. shared_ptr and it's cousin structures are nice because they define all the copy constructors and assignment operators you'd need, which ... are the most error prone part of C++ to me; C++ Constructors are to C++ what the Kick-off is to American Football.
struct Fun {
// this member behaves in a way I appreciate, save for 2 short-comings:
// - needless allocation event (minor)
// - ref counting is only estimate if shared across threads (major)
std::shared_ptr<int> smartPtr {new int};
// this is the hypothetical object that I'm searching for
// + allocates only a control block for the ref count
// + refCount.unique() respects reality when refs exist across many threads
// I can always count on this being the last reference
std::object_of_desire refCount;
// no explicit copy constructors or assignment operators necessary
// + both members of this class provide this paperwork for me,
// so I can careless toss this Fun object around and it'll move
// as one would expect, making only shallow copies/moves and ref counting
Fun();
~Fun(){
if(refCount.unique()){
smart_assert("I swear refCount truly is unique, on pain of death");
}
}
}
The warnings about thread safety w.r.t. std::shared_ptr are
If you have multiple threads that can access the same pointer object, then you can have a data race if one of those threads modifies the pointer. If each thread has it's own instance, pointing to the same shared state, there are no data races on the shared state.
The final modification of the pointed-to object on a thread does not inter-thread happens before another thread observing a use_count of 1. If nothing is modifying the pointed-to object, there are no data races on the pointed-to object.
Here's your desired type
class ref_count {
public:
bool unique() const { return ptr.use_count() == 1; }
private:
struct empty {};
std::shared_ptr<empty> ptr = std::make_shared<empty>();
};
I am working on a small program that utilizes shared pointers. I have a simple class "Thing", that just is an class with an integer attribute:
class Thing{
public:
Thing(int m){x=m;}
int operator()(){
return x;
}
void set_val(int v){
x=v;
}
int x;
~Thing(){
std::cout<<"Deleted thing with value "<<x<<std::endl;
}
};
I have a simple function "fun", that takes in a shared_ptr instance, and an integer value, index, which just keeps track of which thread is outputting a given value. The function prints out the index value, passed to the function, and the reference count of the shared pointer that was passed in as an argument
std::mutex mtx1;
void fun(std::shared_ptr<Thing> t1,int index){
std::lock_guard <std::mutex> loc(mtx1);
int m=t1.use_count();
std::cout<<index<<" : "<<m<<std::endl;
}
In main,I create one instance of a shared pointer which is a wrapper around a Thing object like so:
std::shared_ptr<Thing> ptr5(nullptr);
ptr5=std::make_shared<Thing>(110);
(declared this way for exception safety).
I then create 3 threads, each of which creates a thread executing the fun() function that takes in as arguments the ptr5 shared pointer and increasing index values:
std::thread t1(fun,ptr5,1),t2(fun,ptr5,2),t3(fun,ptr5,3);
t1.join();
t2.join();
t3.join();
My thought process here is that since each of the shared pointer control block member functions was thread safe, the call to use_count() within the fun() function is not an atomic instruction and therefore requires no locking. However, both running without and without a lock_guard,this still leads to a race condition. I expect to see an output of:
1:2
2:3
3:4
since each thread spawns a new reference to the original shared pointer, the use_count() will be incremented each time by 1 reference. However, my output is still random due to some race condition.
In a multithreaded environment, use_count() is approximate. From cppreference :
In multithreaded environment, the value returned by use_count is approximate (typical implementations use a memory_order_relaxed load)
The control block for shared_ptr is otherwise thread-safe :
All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object.
Seeing an out-of-date use_count() is not an indication that the control-block was corrupted by a race condition.
Note that this does not extend to modifying the pointed object. It does not provide synchronization for the pointed object. Only the state of the shared_ptr and the control block are protected.
This question already has answers here:
About thread-safety of weak_ptr
(5 answers)
Closed 4 years ago.
Below is some sample code showing my use case. I have a PIMPL where the implementation can be shared (it's just a bunch of expensively-produced data) but the implementation can be destroyed when it's no longer needed. An instance of the class HasImpl uses a shared pointer to Impl, and the class definition includes a static weak_ptr to Impl that acts as a "pointer dispenser" to new instances of HasImpl, giving them a handle to the Impl if one exists already.
The sample has two alternatives for calling weak_ptr::lock--one that assumes that the the answer to questions 1-3 below are all "yes", and another that does not. The only reason I'd prefer that weak_ptr::lock is thread-safe is that there could be multiple threads trying to get a copy of the pointer to Impl, and if lock is thread-safe, most threads of execution won't have to pass a static variable definition (where the thread would have to check to see if it were already initialized) and won't have to compete to acquire a mutex.
/* In HasImpl.h */
class HasImpl {
public:
HasImpl();
private:
class Impl;
static std::weak_ptr<Impl> sharedImplDispenser;
std::shared_ptr<Impl> myPtrToSharedImpl;
}
/* In HasImpl.cpp */
class HasImpl::Impl {
public:
Impl(); //constructor that takes a lot of time to run
//Lots of stuff, expensively produced, accessable to HasImpl through a shared_ptr to Impl
}
/* hypothetical constructor if weak_ptr::lock is thread-safe */
HasImpl::HasImpl() : myPtrToSharedImpl{sharedImplDispenser.lock()}
{
if (!myPtrToSharedImpl) {
static std::mutex mtx;
std::lockguard<std::mutex> lck(mtx);
myPtrToSharedImpl = sharedImplDispenser.lock();
if (!myPtrToSharedImpl) {
const auto new_impl{std::make_shared<Impl()};
sharedImplDispenser = new_impl; // the only place in the program where a value is assigned to sharedImplDispenser
myPtrToSharedImpl = new_impl;
}
}
}
/* hypothetical constructor if weak_ptr::lock is not thread-safe */
HasImpl::HasImpl()
{
static std::mutex mtx;
std::lockguard<std::mutex> lck(mtx);
myPtrToSharedImpl = sharedImpl.lock();
if (!myPtrToSharedImpl) {
const auto new_impl{std::make_shared<Impl()};
sharedImplDispenser = new_impl; // the only place in the program where a value is assigned to sharedImplDispenser
myPtrToSharedImpl = new_impl;
}
}
Assuming that std::weak_ptr is not empty and was assigned a pointer sometime in the distant past, will the control block be ok if one thread calls weak_ptr::lock while another thread may be calling weak_ptr::lock?
Is calling weak_ptr::lock while another thread may be assigning a ptr to an empty weak_ptr safe enough? That is, will the value either return nullptr or the new pointer? I don't care if the nullptr is spurious (that is, that assignment has occurred but the other threads don't know about it yet). I just don't want to corrupt the control block or obtain an invalid pointer value from the call.
Is calling weak_ptr::lock while the last shared_ptr to the object is being destroyed thread safe?
If there are problems with 1 through 3, will std::atomic<std::weak_ptr<T>> in C++20 fix the issue?
The standard explicitly says that weak_ptr::lock is "executed atomically". So that answers 1 and 3.
For #2, if you're asking about assigning to the same weak_ptr, then it's a data race. Operations that change a shared state's use_count don't provoke a data race, but copying or manipulating the weak_ptr itself is doing more than just poking at use_count.
But if you're talking about locking one weak_ptr while nulling out a different weak_ptr that are both talking to the same shared state, that's fine. The two only interact through the shared state's count, which is stated to be fine.
And yes, atomic<weak_ptr<T>> would allow you to manipulate the same object from multiple threads.
I believe I've got a good handle on at least the basics of multi-threading in C++, but I've never been able to get a clear answer on locking a mutex around shared resources in the constructor or the destructor. I was under the impression that you should lock in both places, but recently coworkers have disagreed. Pretend the following class is accessed by multiple threads:
class TestClass
{
public:
TestClass(const float input) :
mMutex(),
mValueOne(1),
mValueTwo("Text")
{
//**Does the mutex need to be locked here?
mValueTwo.Set(input);
mValueOne = mValueTwo.Get();
}
~TestClass()
{
//Lock Here?
}
int GetValueOne() const
{
Lock(mMutex);
return mValueOne;
}
void SetValueOne(const int value)
{
Lock(mMutex);
mValueOne = value;
}
CustomType GetValueTwo() const
{
Lock(mMutex);
return mValueOne;
}
void SetValueTwo(const CustomType type)
{
Lock(mMutex);
mValueTwo = type;
}
private:
Mutex mMutex;
int mValueOne;
CustomType mValueTwo;
};
Of course everything should be safe through the initialization list, but what about the statements inside the constructor? In the destructor would it be beneficial to do a non-scoped lock, and never unlock (essentially just call pthread_mutex_destroy)?
Multiple threads cannot construct the same object, nor should any thread be allowed to use the object before it's fully constructed. So, in sane code, construction without locking is safe.
Destruction is a slightly harder case. But again, proper lifetime management of your object can ensure that an object is never destroyed when there's a chance that some thread(s) might still use it.
A shared pointer can help in achieving this eg. :
construct the object in a certain thread
pass shared pointers to every thread that needs access to the object (including the thread that constructed it if needed)
the object will be destroyed when all threads have released the shared pointer
But obviously, other valid approaches exist. The key is to keep proper boundaries between the three main stages of an object's lifetime : construction, usage and destruction. Never allow an overlap between any of these stages.
They don't have to be locked in the constructor, as the only way anyone external can get access to that data at that point is if you pass them around from the constructor itself (or do some undefined behaviour, like calling a virtual method).
[Edit: Removed part about destructor, since as a comment rightfully asserts, you have bigger issues if you're trying to access resources from an object which might be dead]