Not sure if this is a style question, or something that has a hard rule...
If I want to keep the public method interface as const as possible, but make the object thread safe, should I use mutable mutexes? In general, is this good style, or should a non-const method interface be preferred? Please justify your view.
On windows e.g. mutexes are mostly fair, but not always. Some implementations e.g. Thread Building Block provide special mutexes that are fair, but these are not based on the OSes native mutexes, and are usually implemented as spin-locks (which have their own caveats).
Mutable data members are those members whose values can be changed in runtime even if the object is of constant type. It is just opposite to constant. Sometimes logic required to use only one or two data member as a variable and another one as a constant to handle the data.
The idea behind mutexes is to only allow one thread access to a section of memory at any one time. If one thread locks the mutex, any other lock attempts will block until the first one unlocks.
The hidden question is: where do you put the mutex protecting your class?
As a summary, let's say you want to read the content of an object which is protected by a mutex.
The "read" method should be semantically "const" because it does not change the object itself. But to read the value, you need to lock a mutex, extract the value, and then unlock the mutex, meaning the mutex itself must be modified, meaning the mutex itself can't be "const".
Then everything's ok. The object can be "const", and the mutex don't need to be:
Mutex mutex ; int foo(const Object & object) { Lock<Mutex> lock(mutex) ; return object.read() ; }
IMHO, this is a bad solution, because anyone could reuse the mutex to protect something else. Including you. In fact, you will betray yourself because, if your code is complex enough, you'll just be confused about what this or that mutex is exactly protecting.
I know: I was victim of that problem.
For encapsulation purposes, you should put the mutex as near as possible from the object it's protecting.
Usually, you'll write a class with a mutex inside. But sooner or later, you'll need to protect some complex STL structure, or whatever thing written by another without mutex inside (which is a good thing).
A good way to do this is to derive the original object with an inheriting template adding the mutex feature:
template <typename T> class Mutexed : public T { public : Mutexed() : T() {} // etc. void lock() { this->m_mutex.lock() ; } void unlock() { this->m_mutex.unlock() ; } ; private : Mutex m_mutex ; }
This way, you can write:
int foo(const Mutexed<Object> & object) { Lock<Mutexed<Object> > lock(object) ; return object.read() ; }
The problem is that it won't work because object
is const, and the lock object is calling the non-const lock
and unlock
methods.
If you believe const
is limited to bitwise const objects, then you're screwed, and must go back to the "external mutex solution".
The solution is to admit const
is more a semantic qualifier (as is volatile
when used as a method qualifier of classes). You are hiding the fact the class is not fully const
but still make sure provide an implementation that keeps the promise that the meaningful parts of the class won't be changed when calling a const
method.
You must then declare your mutex mutable, and the lock/unlock methods const
:
template <typename T> class Mutexed : public T { public : Mutexed() : T() {} // etc. void lock() const { this->m_mutex.lock() ; } void unlock() const { this->m_mutex.unlock() ; } ; private : mutable Mutex m_mutex ; }
The internal mutex solution is a good one IMHO: Having to objects declared one near the other in one hand, and having them both aggregated in a wrapper in the other hand, is the same thing in the end.
But the aggregation has the following pros:
So, keep your mutex as near as possible to the mutexed object (e.g. using the Mutexed construct above), and go for the mutable
qualifier for the mutex.
Apparently, Herb Sutter have the same viewpoint: His presentation about the "new" meanings of const
and mutable
in C++11 is very enlightening:
http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
[Answer edited]
Basically using const methods with mutable mutexes is a good idea (don't return references by the way, make sure to return by value), at least to indicate they do not modify the object. Mutexes should not be const, it would be a shameless lie to define lock/unlock methods as const...
Actually this (and memoization) are the only fair uses I see of the mutable
keyword.
You could also use a mutex which is external to your object: arrange for all your methods to be reentrant, and have the user manage the lock herself : { lock locker(the_mutex); obj.foo(); }
is not that hard to type, and
{ lock locker(the_mutex); obj.foo(); obj.bar(42); ... }
has the advantage it doesn't require two mutex locks (and you are guaranteed the state of the object did not change).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With