Implementing swap for class with std::mutex - c++

Suppose we have a class with a std::mutex:
class Foo
{
std::mutex mutex_;
std::string str_;
// other members etc
public:
friend void swap(Foo& lhs, Foo& rhs) noexcept;
}
What is the appropriate way to implement the swap method here? Is it required/safe to lock each mutex separately and then swap everything? e.g.
void swap(Foo& lhs, Foo& rhs) noexcept
{
using std::swap;
std::lock_guard<std::mutex> lock_lhs {lhs.mutex_}, lock_rhs {rhs.mutex_};
swap(ls.str_, rhs.str_);
// swap everything else
}
I've seen that in C++17, std::lock_guard will have a constructor taking multiple mutexes for avoiding deadlock, but I'm not sure if that's an issue here?

You can use std::lock() to acquire the locks in a non-deadlocking way.
If you want to use std::lock_guard, have them adopt the locks once taken:
std::lock(lhs.mutex_, rhs.mutex_);
std::lock_guard<std::mutex> lock_a(lhs.mutex_, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.mutex_, std::adopt_lock);
//swap actions
swap(ls.str_, rhs.str_);
If you prefer std::unique_lock, then construct them without locking, then call std::lock() to lock them both (this also works with std::lock_guard):
std::unique_lock<std::mutex> lock_a(lhs.mutex_, std::defer_lock);
std::unique_lock<std::mutex> lock_b(rhs.mutex_, std::defer_lock);
std::lock(lock_a, lock_b);
//swap actions
swap(ls.str_, rhs.str_);
In both cases, you should first test for lhs and rhs being the same object, because using std::lock with one mutex twice is undefined behavior:
if (&lhs == &rhs)
return;

I don't think your swap implementation is safe. If another algorithm tries to lock rhs.mutex_ first and then lhs.mutex_, you may end up with a deadlock. Try std::lock() instead.

Related

Best practices for move constructor of a class with mutex-locked caching

I sometimes have classes like this:
class HasMutexLockedCache {
private:
SomeType m_regularData;
mutable std::mutex m_mutex;
mutable std::optaional<T> m_cache;
// Etc.
public:
const m_regularData& getData() const { return m_regularData; }
const T& getExpensiveResult() const {
std::scoped_lock lock(m_mutex);
if (!m_cache) {
m_cache = expensiveFunction();
}
return m_cache;
}
HasMutexLockedCache(const HasMutexLockedCache& other)
: m_regularData(other.m_regularData)
{
std::scoped_lock lock(other.m_mutex);
// I figure we don't have to lock this->m_mutex because
// we are in the c'tor and so nobody else could possibly
// be messing with m_cache.
m_cache = other.m_cache;
}
HasMutexLockedCache(HasMutexLockedCache&& other)
: m_regularData(std::move(other.m_regularData))
{
// What here?
}
HasMutexLockedCache& operator=(HasMutexLockedCache&& other) {
m_regularData = std::move(other.m_regularData);
// Bonus points: What here? Lock both mutexes? One?
// Only lock this->m_mutex depending on how we
// document thread safety?
}
};
My question: what goes in HasMutexLockedCache(HasMutexLockedCache&& other) (and likewise in HasMutexLockedCache& operator=(HasMutexLockedCache&& other)? I think we don't need to lock other.m_mutex because, for other to be an rvalue reference, we know nobody else can see it, just like we don't have to lock this->m_mutex in the c'tor. However, I'd like some guidance. What are the best practices here? Should we be locking other.m_mutex?
You have to remember that l-value objects can be moved using std::move so we do need to lock them too:
HasMutexLockedCache(HasMutexLockedCache&& other) {
std::scoped_lock lock(other.m_mutex);
m_cache = std::move(other.m_cache);
}
HasMutexLockedCache& operator=(HasMutexLockedCache&& other) {
std::scoped_lock lock(m_mutex, other.m_mutex);
m_cache = std::move(other.m_cache);
return *this;
}
I'd like some guidance. What are the best practices here? Should we be locking other.m_mutex?
#Galik's answer explains how one could implement a move-constructor for this, however you should consider whether this is a safe and coherent idea for your abstraction.
If an object contains a std::mutex, generally this means that it may have concurrent accesses at different times which warrant this. If this is the case, then move-semantics can be quite difficult to work with when faced with multi-threading -- since you may have thread A move the contents of m_cache before thread B accesses it, resulting in it reading a moved-from state (which, depending on what the state is being checked, may not be well-defined). These types of errors can be quite complicated to debug, and even harder to reproduce!
Often if you have a type like this, it would be better to be sharing this type across threads explicitly, either with a shared lifetime via shared_ptr, or some form of synchronization from outside so each thread cannot destructively interfere with one another.

How should I deal with mutexes in movable types in C++?

By design, std::mutex is not movable nor copyable. This means that a class A holding a mutex won't receive a default move constructor.
How would I make this type A movable in a thread-safe way?
Let's start with a bit of code:
class A
{
using MutexType = std::mutex;
using ReadLock = std::unique_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
mutable MutexType mut_;
std::string field1_;
std::string field2_;
public:
...
I've put some rather suggestive type aliases in there that we won't really take advantage of in C++11, but become much more useful in C++14. Be patient, we'll get there.
Your question boils down to:
How do I write the move constructor and move assignment operator for this class?
We'll start with the move constructor.
Move Constructor
Note that the member mutex has been made mutable. Strictly speaking this isn't necessary for the move members, but I'm assuming you also want copy members. If that is not the case, there is no need to make the mutex mutable.
When constructing A, you do not need to lock this->mut_. But you do need to lock the mut_ of the object you're constructing from (move or copy). This can be done like so:
A(A&& a)
{
WriteLock rhs_lk(a.mut_);
field1_ = std::move(a.field1_);
field2_ = std::move(a.field2_);
}
Note that we had to default construct the members of this first, and then assign them values only after a.mut_ is locked.
Move Assignment
The move assignment operator is substantially more complicated because you do not know if some other thread is accessing either the lhs or rhs of the assignment expression. And in general, you need to guard against the following scenario:
// Thread 1
x = std::move(y);
// Thread 2
y = std::move(x);
Here is the move assignment operator that correctly guards the above scenario:
A& operator=(A&& a)
{
if (this != &a)
{
WriteLock lhs_lk(mut_, std::defer_lock);
WriteLock rhs_lk(a.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1_ = std::move(a.field1_);
field2_ = std::move(a.field2_);
}
return *this;
}
Note that one must use std::lock(m1, m2) to lock the two mutexes, instead of just locking them one after the other. If you lock them one after the other, then when two threads assign two objects in opposite order as shown above, you can get a deadlock. The point of std::lock is to avoid that deadlock.
Copy Constructor
You didn't ask about the copy members, but we might as well talk about them now (if not you, somebody will need them).
A(const A& a)
{
ReadLock rhs_lk(a.mut_);
field1_ = a.field1_;
field2_ = a.field2_;
}
The copy constructor looks much like the move constructor except the ReadLock alias is used instead of the WriteLock. Currently these both alias std::unique_lock<std::mutex> and so it doesn't really make any difference.
But in C++14, you will have the option of saying this:
using MutexType = std::shared_timed_mutex;
using ReadLock = std::shared_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
This may be an optimization, but not definitely. You will have to measure to determine if it is. But with this change, one can copy construct from the same rhs in multiple threads simultaneously. The C++11 solution forces you to make such threads sequential, even though the rhs isn't being modified.
Copy Assignment
For completeness, here is the copy assignment operator, which should be fairly self explanatory after reading about everything else:
A& operator=(const A& a)
{
if (this != &a)
{
WriteLock lhs_lk(mut_, std::defer_lock);
ReadLock rhs_lk(a.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1_ = a.field1_;
field2_ = a.field2_;
}
return *this;
}
And etc.
Any other members or free functions that access A's state will also need to be protected if you expect multiple threads to be able to call them at once. For example, here's swap:
friend void swap(A& x, A& y)
{
if (&x != &y)
{
WriteLock lhs_lk(x.mut_, std::defer_lock);
WriteLock rhs_lk(y.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
using std::swap;
swap(x.field1_, y.field1_);
swap(x.field2_, y.field2_);
}
}
Note that if you just depend on std::swap doing the job, the locking will be at the wrong granularity, locking and unlocking between the three moves that std::swap would internally perform.
Indeed, thinking about swap can give you insight into the API you might need to provide for a "thread-safe" A, which in general will be different from a "non-thread-safe" API, because of the "locking granularity" issue.
Also note the need to protect against "self-swap". "self-swap" should be a no-op. Without the self-check one would recursively lock the same mutex. This could also be solved without the self-check by using std::recursive_mutex for MutexType.
Update
In the comments below Yakk is pretty unhappy about having to default construct things in the copy and move constructors (and he has a point). Should you feel strongly enough about this issue, so much so that you are willing to spend memory on it, you can avoid it like so:
Add whatever lock types you need as data members. These members must come before the data that is being protected:
mutable MutexType mut_;
ReadLock read_lock_;
WriteLock write_lock_;
// ... other data members ...
And then in the constructors (e.g. the copy constructor) do this:
A(const A& a)
: read_lock_(a.mut_)
, field1_(a.field1_)
, field2_(a.field2_)
{
read_lock_.unlock();
}
Oops, Yakk erased his comment before I had the chance to complete this update. But he deserves credit for pushing this issue, and getting a solution into this answer.
Update 2
And dyp came up with this good suggestion:
A(const A& a)
: A(a, ReadLock(a.mut_))
{}
private:
A(const A& a, ReadLock rhs_lk)
: field1_(a.field1_)
, field2_(a.field2_)
{}
Given there doesn't seem to be a nice, clean, easy way to answer this - Anton's solution I think is correct but its definitely debatable, unless a better answer comes up I would recommend putting such a class on the heap and looking after it via a std::unique_ptr:
auto a = std::make_unique<A>();
Its now a fully movable type and anyone who has a lock on the internal mutex whilst a move happens is still safe, even if its debatable whether this is a good thing to do
If you need copy semantics just use
auto a2 = std::make_shared<A>();
This is an upside-down answer. Instead of embedding "this objects needs to be synchronized" as a base of the type, instead inject it under any type.
You deal with a synchronized object very differently. One big issue is you have to worry about deadlocks (locking multiple objects). It also should basically never be your "default version of an object": synchronized objects are for objects that will be in contention, and your goal should be to minimize contention between threads, not sweep it under the rug.
But synchronizing objects is still useful. Instead of inheriting from a synchronizer, we can write a class that wraps an arbitrary type in synchronization. Users have to jump through a few hoops to do operations on the object now that it is synchronized, but they are not limited to some hand-coded limited set of operations on the object. They can compose multiple operations on the object into one, or have an operation on multiple objects.
Here is a synchronized wrapper around an arbitrary type T:
template<class T>
struct synchronized {
template<class F>
auto read(F&& f) const&->std::result_of_t<F(T const&)> {
return access(std::forward<F>(f), *this);
}
template<class F>
auto read(F&& f) &&->std::result_of_t<F(T&&)> {
return access(std::forward<F>(f), std::move(*this));
}
template<class F>
auto write(F&& f)->std::result_of_t<F(T&)> {
return access(std::forward<F>(f), *this);
}
// uses `const` ness of Syncs to determine access:
template<class F, class... Syncs>
friend auto access( F&& f, Syncs&&... syncs )->
std::result_of_t< F(decltype(std::forward<Syncs>(syncs).t)...) >
{
return access2( std::index_sequence_for<Syncs...>{}, std::forward<F>(f), std::forward<Syncs>(syncs)... );
};
synchronized(synchronized const& o):t(o.read([](T const&o){return o;})){}
synchronized(synchronized && o):t(std::move(o).read([](T&&o){return std::move(o);})){}
// special member functions:
synchronized( T & o ):t(o) {}
synchronized( T const& o ):t(o) {}
synchronized( T && o ):t(std::move(o)) {}
synchronized( T const&& o ):t(std::move(o)) {}
synchronized& operator=(T const& o) {
write([&](T& t){
t=o;
});
return *this;
}
synchronized& operator=(T && o) {
write([&](T& t){
t=std::move(o);
});
return *this;
}
private:
template<class X, class S>
static auto smart_lock(S const& s) {
return std::shared_lock< std::shared_timed_mutex >(s.m, X{});
}
template<class X, class S>
static auto smart_lock(S& s) {
return std::unique_lock< std::shared_timed_mutex >(s.m, X{});
}
template<class L>
static void lock(L& lockable) {
lockable.lock();
}
template<class...Ls>
static void lock(Ls&... lockable) {
std::lock( lockable... );
}
template<size_t...Is, class F, class...Syncs>
friend auto access2( std::index_sequence<Is...>, F&&f, Syncs&&...syncs)->
std::result_of_t< F(decltype(std::forward<Syncs>(syncs).t)...) >
{
auto locks = std::make_tuple( smart_lock<std::defer_lock_t>(syncs)... );
lock( std::get<Is>(locks)... );
return std::forward<F>(f)(std::forward<Syncs>(syncs).t ...);
}
mutable std::shared_timed_mutex m;
T t;
};
template<class T>
synchronized< T > sync( T&& t ) {
return {std::forward<T>(t)};
}
C++14 and C++1z features included.
this assumes that const operations are multiple-reader safe (which is what std containers assume).
Use looks like:
synchronized<int> x = 7;
x.read([&](auto&& v){
std::cout << v << '\n';
});
for an int with synchronized access.
I'd advise against having synchronized(synchronized const&). It is rarely needed.
If you need synchronized(synchronized const&), I'd be tempted to replace T t; with std::aligned_storage, allowing manual placement construction, and do manual destruction. That allows proper lifetime management.
Barring that, we could copy the source T, then read from it:
synchronized(synchronized const& o):
t(o.read(
[](T const&o){return o;})
)
{}
synchronized(synchronized && o):
t(std::move(o).read(
[](T&&o){return std::move(o);})
)
{}
for assignment:
synchronized& operator=(synchronized const& o) {
access([](T& lhs, T const& rhs){
lhs = rhs;
}, *this, o);
return *this;
}
synchronized& operator=(synchronized && o) {
access([](T& lhs, T&& rhs){
lhs = std::move(rhs);
}, *this, std::move(o));
return *this;
}
friend void swap(synchronized& lhs, synchronized& rhs) {
access([](T& lhs, T& rhs){
using std::swap;
swap(lhs, rhs);
}, *this, o);
}
the placement and aligned storage versions are a bit messier. Most access to t would be replaced by a member function T&t() and T const&t()const, except at construction where you'd have to jump through some hoops.
By making synchronized a wrapper instead of part of the class, all we have to ensure is that the class internally respects const as being multiple-reader, and write it in a single-threaded manner.
In the rare cases we need a synchronized instance, we jump through hoops like the above.
Apologies for any typos in the above. There are probably some.
A side benefit to the above is that n-ary arbitrary operations on synchronized objects (of the same type) work together, without having to hard-code it before hand. Add in a friend declaration and n-ary synchronized objects of multiple types might work together. I might have to move access out of being an inline friend to deal with overload conficts in that case.
live example
First of all, there must be something wrong with your design if you want to move an object containing a mutex.
But if you decide to do it anyway, you have to create a new mutex in move constructor, that is e.g:
// movable
struct B{};
class A {
B b;
std::mutex m;
public:
A(A&& a)
: b(std::move(a.b))
// m is default-initialized.
{
}
};
This is thread-safe, because the move constructor can safely assume that its argument isn't used anywhere else, so the locking of the argument isn't required.
Using mutexes and C++ move semantics is an excellent way to safely and efficiently transfer data between threads.
Imagine a 'producer' thread that makes batches of strings and provides them to (one or more) consumers. Those batches could be represented by an object containing (potentially large) std::vector<std::string> objects.
We absolutely want to 'move' the internal state of those vectors into their consumers without unnecessary duplication.
You simply recognize the mutex as part of the object not part of the object's state. That is, you don't want to move the mutex.
What locking you need depends on your algorithm or how generalized your objects are and what range of uses you permit.
If you only ever move from a shared state 'producer' object to a thread-local 'consuming' object you might be OK to only lock the moved from object.
If it's a more general design you will need to lock both. In such a case you need to then consider dead-locking.
If that is a potential issue then use std::lock() to acquire locks on both mutexes in a deadlock free way.
http://en.cppreference.com/w/cpp/thread/lock
As a final note you need to make sure you understand the move semantics.
Recall that the moved from object is left in a valid but unknown state.
It's entirely possible that a thread not performing the move has a valid reason to attempt access the moved from object when it may find that valid but unknown state.
Again my producer is just banging out strings and the consumer is taking away the whole load. In that case every time the producer tries to add to the vector it may find the vector non-empty or empty.
In short if the potential concurrent access to the moved from object amounts to a write it's likely to be OK. If it amounts to a read then think about why it's OK to read an arbitrary state.

Assignment operator in struct after adding mutex in C++

I had a struct type:
struct MyStruct {
int field1;
int field2;
}
Then it became necessary to add a mutex to it to make it shared between threads:
struct MyStruct {
std::mutex _mutex;
int field1;
int field2;
}
But then the compiler (clang) give me these messages on lines where I assign one existing struct to variable of type MyStruct like MyStruct mystruct = p_MyStructMap->at(clientId);:
(1) error: object of type 'MyStruct' cannot be assigned because its copy assignment operator is implicitly deleted
(2) note: copy assignment operator of 'MyStruct' is implicitly deleted because field '_mutex' has a deleted copy assignment operator
std::mutex _mutex
^
(3) /usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/mutex:129:12: note: function has been explicitly marked deleted here
mutex& operator=(const mutex&) = delete;
^
Please, help: How can I rewrite struct or may be the program logic to use mutexes for this struct?
Assuming that _mutex was added to protect the other fields from being simultaneously accessed, you will need to lock the mutex(s) during assignment, unless you can otherwise guarantee that multiple threads aren't accessing either side of the assignment expression:
x = y;
If any other thread is reading or writing to x simultaneously, you have a race without locking.
If any other thread is writing to y simultaneously, you have a race without locking.
If you do indeed need to lock, it is not quite as simple as locking both sides:
MyStruct& operator=(const MyStruct& o)
{
if (this != &o)
{
// WRONG! DO NOT DO THIS!!!
std::lock_guard<std::mutex> lhs_lk(_mutex);
std::lock_guard<std::mutex> rhs_lk(o._mutex);
field1 = o.field1;
field2 = o.field2;
}
return *this;
}
The reason you should not do this is imagine thread A does this:
x = y;
And simultaneously thread B does this:
y = x;
Now you have the potential for deadlock. Imagine thread A locks x._mutex, and then thread B locks y._mutex. Now both thread A and B will block trying to lock their o._mutex, and neither thread will ever succeed because it is waiting for the other thread to release it.
The correct formulation of the assignment operator can look like this:
MyStruct& operator=(const MyStruct& o)
{
if (this != &o)
{
std::lock(_mutex, o._mutex);
std::lock_guard<std::mutex> lhs_lk(_mutex, std::adopt_lock);
std::lock_guard<std::mutex> rhs_lk(o._mutex, std::adopt_lock);
field1 = o.field1;
field2 = o.field2;
}
return *this;
}
The job of std::lock(m1, m2, ...) is to lock all of the mutexes in some magical way that does not deadlock. For more detail than you probably want to know about how that is done, you can read Dining Philosophers Rebooted.
Now with _mutex and o._mutex locked, you simply need to make their unlocking exception safe by having the lock_guards adopt ownership of their mutexes. That is, they will no longer try to lock their mutex on construction, but they will still unlock them on destruction. The lock_guard constructors themselves throw nothing, so this is all exception safe.
Oh, you will also have to store _mutex as a mutable data member else you won't be able to lock and unlock it on the rhs.
In C++14 a potential optimization is available if you want to try it: You can "write-lock" this->_mutex and "read-lock" o._mutex. This would allow multiple threads to simultaneously assign from a common rhs if no threads are assigning to that rhs. In order to do that you need to have MyStruct store a std::shared_timed_mutex instead of a std::mutex:
#include <mutex>
#include <shared_mutex>
struct MyStruct
{
using MutexType = std::shared_timed_mutex;
using ReadLock = std::shared_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
mutable MutexType _mutex;
int field1;
int field2;
MyStruct& operator=(const MyStruct& o)
{
if (this != &o)
{
WriteLock lhs_lk(_mutex, std::defer_lock);
ReadLock rhs_lk(o._mutex, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1 = o.field1;
field2 = o.field2;
}
return *this;
}
};
This is similar to as before except that we need to change the type of the mutex, and now the lhs locks with unique_lock (which write-locks the lhs mutex) and the rhs locks with shared_lock (which read-locks the rhs mutex). Here we also use std::defer_lock to construct the locks but to tell the locks that the mutex isn't locked yet, and don't lock on construction. And then our old friend std::lock(m1, m2) is used to tell both locks to lock at the same time without deadlock. Yes, std::lock works with both mutex and lock types. Anything that has member lock(), try_lock(), and unlock(), will work with std::lock(m1, m2, ...).
Note, the C++14 technique isn't definitely an optimization. You will have to measure to confirm or deny that it is. For something as simple as MyStruct it probably isn't an optimization, except perhaps for some special usage patterns. The C++11 technique with std::mutex continues to be a valuable tool in the toolbox, even in C++14.
For ease in switching back and fourth between the mutex and shared_timed_mutex, this latest example makes use of type aliases, which can be easily changed. One only has to change two lines to switch back to mutex:
using MutexType = std::shared_timed_mutex;
using ReadLock = std::sharedunique_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
std::mutex variables/members cannot be assigned, therefore, you get the error.
Try something like this:
struct MyStruct {
std::mutex _mutex;
int field1;
int field2;
MyStruct &operator=(const MyStruct &o) {
field1=o.field1;
field2=o.field2;
return *this;
}
};
However, this will not lock the mutex during the assignment. Depending on the context you may need to add locking inside the assignment operator or in the invoking function.

operator Overloading and mutex

I want to create mutex(boost) inside the operator (=)
the code below is thread safe?
ub_int & operator=(const int x)
{
mutex::scoped_lock mylock(MX_ub, defer_lock);
mylock.lock();
this->v=x;
mylock.unlock();
return *this;
}
The code seems to be correct, but
as described in the comments, you should not use the defer_lock parameter
At least in the example you probably don't need a mutex at all, as a write to an int is usually an atomic operation

Designing a thread-safe copyable class

The straightforward way to make a class threadsafe is to add a mutex attribute and lock the mutex in the accessor methods
class cMyClass {
boost::mutex myMutex;
cSomeClass A;
public:
cSomeClass getA() {
boost::mutex::scoped_lock lock( myMutex );
return A;
}
};
The problem is that this makes the class non-copyable.
I can make things work by making the mutex a static. However, this means that every instance of the class blocks when any other instance is being accessed, because they all share the same mutex.
I wonder if there is a better way?
My conclusion is that there is no better way. Making a class thread-safe with private static mutex attribute is ‘best’: - it is simple, it works, and it hides the awkward details.
class cMyClass {
static boost::mutex myMutex;
cSomeClass A;
public:
cSomeClass getA() {
boost::mutex::scoped_lock lock( myMutex );
return A;
}
};
The disadvantage is that all instances of the class share the same mutex and so block each other unnecessarily. This cannot be cured by making the mutex attribute non-static ( so giving each instance its own mutex ) because the complexities of copying and assignment are nightmarish, if done properly.
The individual mutexes, if required, must be managed by an external non-copyable singleton with links established to each instance when created.
Thanks for all the responses.
Several people have mentioned writing my own copy constructor and assignment operator. I tried this. The problem is that my real class has many attributes which are always changing during development. Maintaining both the copy constructor and assignmet operator is tedious and error-prone, with errors creating hard to find bugs. Letting the compiler generate these for complex class is an enormous time saver and bug reducer.
Many responses are concerned about making the copy constructor and assignment operator thread-safe. This requirement adds even more complexity to the whole thing! Luckily for me, I do not need it since all the copying is done during set-up in a single thread.
I now think that the best approach would be to build a tiny class to hold just a mutex and the critical attributes. Then I can write a small copy constructor and assignment operator for the critical class and leave the compiler to look after all the other attributes in the main class.
class cSafe {
boost::mutex myMutex;
cSomeClass A;
public:
cSomeClass getA() {
boost::mutex::scoped_lock lock( myMutex );
return A;
}
(copy constructor)
(assignment op )
};
class cMyClass {
cSafe S;
( ... other attributes ... )
public:
cSomeClass getA() {
return S.getA();
}
};
You can define your own copy constructor (and copy assignment operator). The copy constructor would probably look something like this:
cMyClass(const cMyClass& x) : A(x.getA()) { }
Note that getA() would need to be const-qualified for this to work, which means the mutex would need to be mutable; you could make the parameter a non-const reference, but then you can't copy temporary objects, which usually isn't desirable.
Also, consider that it isn't always a good idea to perform locking at such a low level: if you lock the mutex in the accessor and the mutator functions, you lose a lot of functionality. For example, you can't perform a compare-and-swap because you can't get and set the member variable with a single lock of the mutex, and if you have multiple data members controlled by the mutex, you can't access more than one of them with the mutex locked.
As simple as the question might be, getting it right is not so simple. For starters we can work the easy copy constructor:
// almost pseudo code, mutex/lock/data types are synthetic
class test {
mutable mutex m;
data d;
public:
test( test const & rhs ) {
lock l(m); // Lock the rhs to avoid race conditions,
// no need to lock this object.
d = rhs.d; // perform the copy, data might be many members
}
};
Now creating an assignment operator is more complex. The first thing that comes to mind is just doing the same, but in this case locking both the lhs and rhs:
class test { // wrong
mutable mutex m;
data d;
public:
test( test const & );
test& operator=( test const & rhs ) {
lock l1( m );
lock l2( rhs.m );
d = rhs.d;
return *this;
}
};
Simple enough, and wrong. While we are guaranteeing single threaded access to the objects (both) during the operation, and thus we get no race conditions, we have a potential deadlock:
test a, b;
// thr1 // thr2
void foo() { void bar() {
a = b; b = a;
} }
And that is not the only potential deadlock, the code is not safe for self assignment (most mutex are not recursive, and trying to lock the same mutex twice will block the thread). The simple thing to solve is the self assignment:
test& test::operator=( test const & rhs ) {
if ( this == &rhs ) return *this; // nothing to do
// same (invalid) code here
}
For the other part of the problem you need to enforce an order in how the mutexes are acquired. That could be handled in different ways (storing a unique identifier per object an comparing...)
test & test::operator=( test const & rhs ) {
mutex *first, *second;
if ( unique_id(*this) < unique_id(rhs ) {
first = &m;
second = &rhs.m;
} else {
first = &rhs.m;
second = &rhs.m;
}
lock l1( *first );
lock l2( *second );
d = rhs.d;
}
The specific order is not as important as the fact that you need to ensure the same order in all uses, or else you will potentially deadlock the threads. As this is quite common, some libraries (including the upcoming c++ standard) have specific support for it:
class test {
mutable std::mutex m;
data d;
public:
test( const test & );
test& operator=( test const & rhs ) {
if ( this == &rhs ) return *this; // avoid self deadlock
std::lock( m, rhs.m ); // acquire both mutexes or wait
std::lock_guard<std::mutex> l1( m, std::adopt_lock ); // use RAII to release locks
std::lock_guard<std::mutex> l2( rhs.m, std::adopt_lock );
d = rhs.d;
return *this;
}
};
The std::lock function will acquire all locks passed in as argument and it ensures that the order of acquisition is the same, ensuring that if all code that needs to acquire these two mutexes does so by means of std::lock there will be no deadlock. (You can still deadlock by manually locking them somewhere else separately). The next two lines store the locks in objects implementing RAII so that if the assignment operation fails (exception is thrown) the locks are released.
That can be spelled differently by using std::unique_lock instead of std::lock_guard:
std::unique_lock<std::mutex> l1( m, std::defer_lock ); // store in RAII, but do not lock
std::unique_lock<std::mutex> l2( rhs.m, std::defer_lock );
std::lock( l1, l2 ); // acquire the locks
I just thought of a different much simpler approach that I am sketching here. The semantics are slightly different, but may be enough for many applications:
test& test::operator=( test copy ) // pass by value!
{
lock l(m);
swap( d, copy.d ); // swap is not thread safe
return *this;
}
}
There is a semantic difference in both approaches, as the one with copy-and-swap idiom has a potential race condition (that might or might not affect your application, but that you should be aware of). Since both locks are never held at once, the objects may change between the time the first lock is released (copy of the argument completes) and the second lock is acquired inside operator=.
For an example of how this might fail, consider that data is an integer and that you have two objects initialized with the same integer value. One thread acquires both locks and increments the values, while another thread copies one of the objects into the other:
test a(0), b(0); // ommited constructor that initializes the ints to the value
// Thr1
void loop() { // [1]
while (true) {
std::unique_lock<std::mutex> la( a.m, std::defer_lock );
std::unique_lock<std::mutex> lb( b.m, std::defer_lock );
std::lock( la, lb );
++a.d;
++b.d;
}
}
// Thr1
void loop2() {
while (true) {
a = b; // [2]
}
}
// [1] for the sake of simplicity, assume that this is a friend
// and has access to members
With the implementations of operator= that perform simultaneous locks on both objects, you can assert at any one given time (doing it thread safely by acquiring both locks) that a and b are the same, which seems to be expected by a cursory read of the code. That does not hold if operator= is implemented in terms of the copy-and-swap idiom. The issue is that in the line marked as [2], b is locked and copied into a temporary, then the lock is released. The first thread can then acquire both locks at once, and increment both a and b before a is locked by the second thread in [2]. Then a is overwritten with the value that b had before the increment.
The simple fact is that you cannot make a class thread safe by spewing mutexes at the problem. The reason that you can't make this work is because it doesn't work, not because you're doing this technique wrong. This is what everyone noticed when multithreading first came and started slaughtering COW string implementations.
Thread design occurs at the application level, not on a per-class basis. Only specific resource management classes should have thread-safety on this level- and for them you need to write explicit copy constructors/assignment operators anyway.