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]
Related
Say we have something along the lines of the following pseudocode, with the goal of achieving both concurrency and taking advantage of RAII:
class Foo {
public:
vector<int> nums;
mutex lock;
};
class Bar {
public:
Bar(Foo &foo) : m_foo(foo)
{
lock_guard<mutex>(foo.lock);
m_num = foo.nums.back();
foo.nums.pop_back();
}
~Bar()
{
lock_guard<mutex>(foo.lock);
foo.nums.push_back(m_num);
}
private:
Foo &m_foo;
int m_num;
};
Then, say we may have any number of instances of Bar, with the idea being that when they go out of scope, the destructor will return their held "resource" to the controller Foo class. However, we also need to ensure thread safety, hence the locks. I'm a little wary of this design, however, since taking a mutex in a destructor seems like a bad idea intuitively. Am I overthinking things, or if not, is there a better way to take advantage of RAII here?
There's nothing inherently wrong with locking a mutex in a destructor. For instance, shared resources might need to be made thread-safe. Releasing ownership of a shared resource, then, might require locking a mutex. If RAII just fell apart in multithreaded programming, it wouldn't be a very useful tool. Indeed, access to a std::shared_ptr's control block is thread safe, including when decrementing the reference counter during the shared pointer's destruction. Apparently this is usually implemented with atomic operations rather than a mutex lock (don't quote me on this), but the context is the same: you're releasing ownership of a shared resource during destruction, and that has to be recorded in a thread-safe way.
However, keep in mind: locking a mutex can throw an exception, and you should (almost) always absorb exceptions in destructors with try/catch. Otherwise, if the stack is already unwinding due to another exception, the program will terminate immediately and irrecoverably, regardless of whether the calling code is equipped to absorb the original exception and / or the destructor's exception.
But there might be a way to restructure your code to avoid the issue entirely: A Bar doesn't actually need a reference to a Foo; it only needs an int. In your code, the Bar requests an int from a given Foo. When the Bar is destroyed, it needs to give the int back to the Foo so that it can be recycled; this requires storing an internal reference to the Foo throughout its lifetime so that it can communicate with it during destruction. Instead, consider giving the int to the Bar directly upon construction, and taking the int away from it upon destruction. This is the driving principle behind dependency injection, which constitutes the 'D' in "SOILD". Consequently, it brings with it all of the typical advantages of dependency injection (e.g., improving testability of the Bar class).
For instance, this logic could be tracked in a larger class which manages a Foo object along with all of its associated Bar objects. Here's some pseudocode, but the exact interface details will depend on your application:
class BarPool:
Foo foo;
Map<int, Bar> bars;
mutex m;
BarPool(Foo foo) : foo(foo) {}
int add_bar():
lock m;
// Note: foo.pop() should probably be made thread-safe
// by internally locking / unlocking foo's mutex
int i = foo.pop()
bars.add(i, new Bar(i));
unlock m;
return i
void remove_bar(int i):
lock m;
// foo.push() should also probably be made thread-safe
bars.remove(i)
foo.push(i)
unlock m;
...
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 was reading about thread safety of std::shared_ptr and about the atomic operations overloads it provides and was wondering regarding a specific use case of it in a class.
From my understanding of the thread safety that is promised from shared_ptr, it is safe to have a get method like so:
class MyClass
{
std::shared_ptr<int> _obj;
public:
void start()
{
std::lock_guard<std::mutex> lock(_mtx);
_obj = std::make_shared<int>(1);
}
void stop()
{
std::lock_guard<std::mutex> lock(_mtx);
_obj.reset();
}
std::shared_ptr<int> get_obj() const
{
return _obj; //Safe (?)
}
};
The getter should be safe since the object will either be initializes or empty at any point from any thread.
But what if I want to throw an exception if the object is empty, I need to check it before returning it, do I have to put a lock there now (since stop() might be called between the if and the return)? Or is it possible to use the locking mechanism of the shared pointer and not use a lock in this method:
std::shared_ptr<int> get_obj() const
{
auto tmp = _obj;
if(!tmp) throw std::exception();
return tmp;
}
std::shared_ptr instances are not thread safe. Multiple instances all pointing to the same object can be modified from multiple threads but a single instance is not thread safe. See https://en.cppreference.com/w/cpp/memory/shared_ptr:
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. 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; the shared_ptr overloads of atomic functions can be used to prevent the data race.
You therefore either need to lock your mutex in your get_obj method or use std::atomic_load and std::atomic_store in your start and stop methods
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.
Suppose I have a class:
class WonnaBeMovedClass
{
public:
WonnaBeMovedClass(WonnaBeMovedClass&& other);
void start();
private:
void updateSharedData();
std::vector<int> _sharedData;
std::thread _threadUpdate;
std::mutex _mutexShaderData;
//other stuff
};
WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other)
{
_sharedData = std::move(other._sharedData);
_threadUpdate = std::move(other._threadUpdate);
_mutexShaderData = std::move(other._mutexShaderData); //won't compile.
}
void WonnaBeMovedClass::start()
{
_threadUpdate = std::thread(&updateSharedData, this);
}
void WonnaBeMovedClass::updateSharedData()
{
std::lock_guard<std::mutex> lockSharedData(_mutexShaderData);
for (auto& value : _sharedData)
{
++value;
}
}
It won't compile because mutex cannot be moved. It doesn't make sense.
Then I thought that it is possible to workaround this by using pointers instead of actual variables and came up with the following:
class WonnaBeMovedClass
{
public:
WonnaBeMovedClass(WonnaBeMovedClass&& other);
void start();
private:
void updateSharedData();
std::vector<int> _sharedData;
std::unique_ptr<std::thread> _threadUpdate //pointer;
std::unique_ptr<std::mutex> _mutexShaderData //pointer;
//other stuff
};
WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other)
{
_sharedData = std::move(other._sharedData);
_threadUpdate = std::move(other._threadUpdate);
_mutexShaderData = std::move(other._mutexShaderData); //won't compile.
}
void WonnaBeMovedClass::start()
{
_threadUpdate = std::make_unique<std::thread>(&updateSharedData, this);
}
void WonnaBeMovedClass::updateSharedData()
{
std::lock_guard<std::mutex> lockSharedData(*_mutexShaderData);
for (auto& value : _sharedData)
{
++value;
}
}
So now when I:
WonnaBeMovedClass object1;
WonnaBeMovedClass object2;
//do stuff
object1 = std::move(object2);
I actually move addresses of both mutex and thread.
It makes more sense now... Or not?
The thread is still working with the data of object1, not object2, so it still doesn't make any sense.
I may have moved the mutex, but the thread is unaware of object2. Or is it?
I am unable to find the answer so I am asking you for help.
Am I doing something completely wrong and copying/moving threads and mutexes is just a bad design and I should rethink the architecture of the program?
Edit:
There was a question about actual purpose of the class. It is actually a TCP/IP client (represented as a class) that holds:
latest data from the server (several data tables, similar to std::vector).
contains methods that manage threads (update state, send/receive messages).
More that one connection could be established at a time, so somewhere in a code there is a std::vector<Client> field that represents all active connections.
Connections are determined by the configuration file.
//read configurations
...
//init clients
for (auto& configuration : _configurations)
{
Client client(configuration);
_activeClients.push_back(client); // this is where compiler reminded me that I am unable to move my object (aka WonnaBeMovedClass object).
}}
I've changed _activeClients from std::vector<Client> to std::vector<std::unique_ptr<Client>> and modified initialization code to create pointer objects instead of objects directly and worked around my issues, but the question remained so I decided to post it here.
Let's break the issue in two.
Moving mutexes. This cannot be done because mutexes are normally implemented in terms of OS objects which must have fixed addresses. In other words, the OS (or the runtime library, which is the same as the OS for our purposes) keeps an address of your mutex. This can be worked around by storing (smart) pointers to mutexes in your code, and moving those instead. The mutex itself doesn't move. Thread objects can be moved so there's no issue.
Moving your own data whereas some active code (a thread or a running function or a std::function stored somewhere or whatever) has the address of your data and can access it. This is actually very similar to the previous case, only instead of the OS it's your own code that holds on the data. The solution, as before, is in not moving your data. Store and move a (smart) pointer to the data instead.
To summarise,
class WonnaBeMovedClass
{
public:
WonnaBeMovedClass
(WonnaBeMovedClass&& other);
void start();
private:
struct tdata {
std::vector<int> _sharedData;
std::thread _threadUpdate;
std::mutex _mutexShaderData;
};
std::shared_ptr<tdata> data;
static void updateSharedData(std::shared_ptr<tdata>);
};
void WonnaBeMovedClass::start()
{
_threadUpdate = std::thread(&updateSharedData, data);
}
It makes more sense now... Or not?
Not really.
If an std::mutex gets moved, the other threads will not be aware of the modification of memory address of that mutex! This discards thread safety.
However, a solution with std::unique_ptr exists in Copy or Move Constructor for a class with a member std::mutex (or other non-copyable object)?
Last, but not least C++14 seems to have something to bring into the play. Read more in How should I deal with mutexes in movable types in C++?
WonnaBeMovedClass is a handle holding thread and mutex, so it is not a bad design to provide them with a move semantics (but not copy).
The second solutions looks fine, but don't forget about proper resource management for your mutex (construct and destruct). I don't really undertand the real life purpose of the class, so depending on the whole solution desing, it might be better to use shared_ptr instead of unique_ptr (in case that multiple WonnaBeMovedClass can share same mutex).
std::thread is itself a handle to system thread, so it doesn't have to be wrapped in a pointer, resource management (i.e OS thread handle) is managed by the standard library itself.
Note that mutex are actually kernel objects (usually implemented as an opaque pointer, for example in Windows API), and thus should not be modified or changed by user code in any way.