In the classic problem of transferring money from one bank account to another, the accepted solution (I believe) is to associate a mutex with each bank account, then lock both b
Personally I am a fan of the LockingPtr paradigm (this article is quite outdated and I won't personally follow all of its advice) :
struct thread_safe_account_pointer {
thread_safe_account_pointer( std::mutex & m,Account * acc) : _acc(acc),_lock(m) {}
Account * operator->() const {return _acc;}
Account& operator*() const {return *_acc;}
private:
Account * _acc;
std::lock_guard _lock;
};
And implement the classes which contains an Account
object like this:
class SomeTypeWhichOwnsAnAccount {
public:
thread_safe_account_pointer get_and_lock_account() const {return thread_safe_account_pointer(mutex,&_impl);}
//Optional non thread-safe
Account* get_account() const {return &_impl;}
//Other stuff..
private:
Account _impl;
std::mutex mutex;
};
Pointers may be replaced with smart pointers if suitable, and you will probably need a const_thread_safe_account_pointer
(or even better a general purpose template thread_safe_pointer
class)
Why is this better than monitors (IMO)?
get_account
). Having both a get_and_lock()
and a get()
function forces you to think about thread-safety.thread_safe_pointer
) or is thread-safety-agnostic (use Account&
).thread_safe_pointer
has a quite different semantic from monitors:Consider a MyVector
class which implements thread-safety via monitors, and the following code:
MyVector foo;
// Stuff.. , other threads are using foo now, pushing and popping elements
int size = foo.size();
for (int i=0;i < size;++i)
do_something(foo[i]);
IMO code like this is really bad, because it makes you feel safe
thinking that monitors will take care of thread-safety for you, while here we have a race condition which is incredibly difficult to spot.