Reputation: 663
I've run into code that simplified looks like this
inline someClass* otherClass::getSomeClass()
{
if (m_someClass)
return m_someClass.get();
std::unique_lock<std::shared_mutex> lock(m_lock);
if (m_someClass)
return m_someClass.get();
m_someClass= std::make_unique<someClass>(this);
return m_someClass.get();
}
So it seems it's a pattern to be sure thread safety of creation of someClass object. I don't have much experience in multithreading, but this code doesn't look nice to me. Is there some other way to rewrite this or it's a way it should be?
Upvotes: 0
Views: 128
Reputation: 275800
The biggest problem here is that you are violating the C++ memory model. In the C++ memory model, a write operation and a read operation to the same data must be synchronized.
The m_someClass
at the front is reading what is written to in the mutex.
It is possible that the operator bool
on m_someClass
is atomic somehow.
Also, your code doesn't handle the object ever being destroyed.
If it is atomic, then you should possibly be using atomic operations to update it and not a lock. Such a pattern can result in "wasted" objects being created; often this is worth the cost of removing the lock.
make m_someClass
be std::atomic<std::shared_ptr<someClass>>
.
Return std::shared_ptr<someClass>
from getSomeClass
.
auto existing = m_someClass.load();
if (existing)
return existing;
auto created = std::make_shared<someClass>(this);
if (
m_someClass.compare_exchange_strong(existing, created)
) {
return created;
} else {
return existing;
}
Two threads can both create a new someClass
if they both try to get at the same time, but only one will persist, the other will be discarded, and the function will return the one that persists.
Upvotes: 1