Is Poco RefCountedObject thread safe? - c++

Poco RefCountedObject offer 2 interfaces:
inline void RefCountedObject::duplicate() const
{
++_counter;
}
inline void RefCountedObject::release() const throw()
{
try
{
if (--_counter == 0) delete this;
}
catch (...)
{
poco_unexpected();
}
}
With:
class Foundation_API RefCountedObject
/// A base class for objects that employ
/// reference counting based garbage collection.
///
/// Reference-counted objects inhibit construction
/// by copying and assignment.
{
public:
RefCountedObject();
/// Creates the RefCountedObject.
/// The initial reference count is one.
void duplicate() const;
/// Increments the object's reference count.
void release() const throw();
/// Decrements the object's reference count
/// and deletes the object if the count
/// reaches zero.
int referenceCount() const;
/// Returns the reference count.
protected:
virtual ~RefCountedObject();
/// Destroys the RefCountedObject.
private:
RefCountedObject(const RefCountedObject&);
RefCountedObject& operator = (const RefCountedObject&);
mutable AtomicCounter _counter;
};
Note that:
mutable AtomicCounter _counter;
my question is if I use a RefCountedObject in multithreading is it safe?
In my opinion it's not because only --_counter is atomic but if(--_count) is not atomic and could lead to having a reference to a deleted object.
For example let say I have 2 threads A and B one executing duplicate and the other release, the execution sequence is as follow:
B start executing release and reach --_counter
A start executing duplicate and reach ++_counter
At this point _counter=1
B execute --_counter and return the result to be evaluated by the if (which is 0 && counter is 0 now)
B is preempted and stop before the branching statement (the if)
A execute _counter++ and return a reference to the object
B continue and evaluate the branching statement with value 0 and delete the object
We end up with A having a reference to a deleted object.
even if the mutable keyword force the compiler to not optimize the _counter it won't help in multithreading
Am I missing something?

Strictly from looking at duplicate and release in isolation, the above is correct.
However, if you have two threads, both having a pointer to the same RefCountedObject, you either should have separate AutoPtr instances in each thread (which implies the _counter is > 1 to begin with), or, if you share the same AutoPtr, it must be protected by a mutex, if at least one thread can change the AutoPtr (which would result in release() being called). Needless to say sharing an mutable AutoPtr between multiple threads is a recipe for disaster.
If you manage RefCountedObject by manually calling duplicate() and release() (which I would not recommend), you should follow POCO's reference counting rules and be very careful.
So, for well-behaved code, RefCountedObject together with AutoPtr is safe for multithreaded use.

It is safe because the counter is based on std::atomic AND std::atomic is using by default std::memory_order_seq_cst for its operation.
If std::atomic was using std::memory_order_relaxed, then the implementation would be buggy/unsafe.
The downside, is that std::memory_order_seq_cst is "slow". There are faster implementations than Poco's, but less simple, by using some different code and different memory ordering commands (https://en.cppreference.com/w/cpp/atomic/memory_order)

Related

Does C++ offer a thread-safe reference counter?

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>();
};

Thread Safety of Shared Pointers' Control Block

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.

Is std::weak_ptr<T>::lock thread-safe? [duplicate]

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.

Is reference counting thread safe

For example consider
class ProcessList {
private
std::vector<std::shared_ptr<SomeObject>> list;
Mutex mutex;
public:
void Add(std::shared_ptr<SomeObject> o) {
Locker locker(&mutex); // Start of critical section. Locker release so will the mutex - In house stuff
list.push_back(std::make_shared<SomeObject>(o).
}
void Remove(std::shared_ptr<SomeObject> o) {
Locker locker(&mutex); // Start of critical section. Locker release so will the mutex - In house stuff
// Code to remove said object but indirectly modifying the reference count in copy below
}
void Process() {
std::vector<std::shared_ptr<SomeObject>> copy;
{
Locker locker(&mutes);
copy = std::vector<std::shared_ptr<SomeObject>>(
list.begin(), list.end()
)
}
for (auto it = copy.begin(), it != copy.end(); ++it) {
it->Procss(); // This may take time/add/remove to the list
}
}
};
One thread runs Process. Multiple threads run add/remove.
Will the reference count be safe and always correct - or should a mutex be placed around that?
Yes, the standard (at ยง20.8.2.2, at least as of N3997) that's intended to require that the reference counting be thread-safe.
For your simple cases like Add:
void Add(std::shared_ptr<SomeObject> o) {
Locker locker(&mutex);
list.push_back(std::make_shared<SomeObject>(o).
}
...the guarantees in the standard are strong enough that you shouldn't need the mutex, so you can just have:
void Add(std::shared_ptr<SomeObject> o) {
list.push_back(std::make_shared<SomeObject>(o).
}
For some of your operations, it's not at all clear that thread-safe reference counting necessarily obviates your mutex though. For example, inside of your Process, you have:
{
Locker locker(&mutes);
copy = std::vector<std::shared_ptr<SomeObject>>(
list.begin(), list.end()
)
}
This carries out the entire copy as an atomic operation--nothing else can modify the list during the copy. This assures that your copy gives you a snapshot of the list precisely as it was when the copy was started. If you eliminate the mutex, the reference counting will still work, but your copy might reflect changes made while the copy is being made.
In other words, the thread safety of the shared_ptr only assures that each individual increment or decrement is atomic--it doesn't assure that manipulations of the entire list are atomic as the mutex does in this case.
Since your list is actually a vector, you should be able to simplify the copying code a bit to just copy = list.
Also note that your Locker seems to be a subset of what std::lock_guard provides. It appears you could use:
std::lock_guard<std::mutex> locker(&mutes);
...in its place quite easily.
It will be an overhead to have a mutex for reference counting.
Internally, mutexes use atomic operations, basically a mutex does internal thread safe reference counting. So you can just use atomics for your reference counting directly instead of using a mutex and essentially doing double the work.
Unless your CPU architecture have an atomic increment/decrement and you used that for reference count, then, no, it's not safe; C++ makes no guarantee on the thread safety of x++/x-- operation on any of its standard types.
Use atomic<int> if your compiler supports them (C++11), otherwise you'll need to have the lock.
Further references:
https://www.threadingbuildingblocks.org/docs/help/tbb_userguide/Atomic_Operations.htm

Locking shared resources in constructor and destructor

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]