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
Your problem is to associate the locking with the data. In my eyes, stuffing the mutex
into the object is fine. You could go even further, making the objects essentially into monitors: Lock to enter a function member, unlock when leaving.
Check out Herb Sutter talk at C++ and Beyond 2012: C++ Concurrency. He shows example of Monitor Object-like implementation in C++11.
monitor<Account> m[2];
transaction([](Account &x,Account &y)
{
// Both accounts are automaticaly locked at this place.
// Do whatever operations you want to do on them.
x.money-=100;
y.money+=100;
},m[0],m[1]);
// transaction - is variadic function template, it may accept many accounts
Implementation:
LIVE DEMO
#include <iostream>
#include <utility>
#include <ostream>
#include <mutex>
using namespace std;
typedef int Money;
struct Account
{
Money money = 1000;
// ...
};
template<typename T>
T &lvalue(T &&t)
{
return t;
}
template<typename T>
class monitor
{
mutable mutex m;
mutable T t;
public:
template<typename F>
auto operator()(F f) const -> decltype(f(t))
{
return lock_guard<mutex>(m),
f(t);
}
template<typename F,typename ...Ts> friend
auto transaction(F f,const monitor<Ts>& ...ms) ->
decltype(f(ms.t ...))
{
return lock(lvalue(unique_lock<mutex>(ms.m,defer_lock))...),
f(ms.t ...);
}
};
int main()
{
monitor<Account> m[2];
transaction([](Account &x,Account &y)
{
x.money-=100;
y.money+=100;
},m[0],m[1]);
for(auto &&t : m)
cout << t([](Account &x){return x.money;}) << endl;
}
Output is:
900
1100