在将钱从一个银行账户转移到另一个银行账户的经典问题中,接受的解决方案(我相信)是将互斥锁与每个银行账户相关联,然后在从一个账户中提取资金并将其存入另一个账户之前锁定两者.乍一看,我这样做:
class Account {
public:
void deposit(const Money& amount);
void withdraw(const Money& amount);
void lock() { m.lock(); }
void unlock() { m.unlock(); }
private:
std::mutex m;
};
void transfer(Account& src, Account& dest, const Money& amount)
{
src.lock();
dest.lock();
src.withdraw(amount);
dest.deposit(amount);
dest.unlock();
src.unlock();
}
Run Code Online (Sandbox Code Playgroud)
但手动解锁气味.我可以让互斥公众,然后用std::lock_guard中transfer,但公共数据成员闻到了.
要求std::lock_guard是它的类型满足BasicLockable要求,这只是调用lock并且unlock有效.Account满足这一要求,所以我可以只使用std::lock_guard与Account直接:
void transfer(Account& src, Account& dest, const Money& amount)
{
std::lock_guard<Account> g1(src);
std::lock_guard<Account> g2(dest);
src.withdraw(amount);
dest.deposit(amount);
}
Run Code Online (Sandbox Code Playgroud)
这似乎没问题,但我以前从未见过这种事情,并且复制锁定和解锁互斥锁Account本身就有点臭.
在这种情况下,将互斥锁与其保护的数据相关联的最佳方法是什么?
更新:在下面的评论中指出,std::lock可以用来避免死锁,但我忽视的是,std::lock依赖于是否存在try_lock功能(除了对于lock和unlock).添加try_lock到Account界面似乎是一个相当严重的黑客.因此,似乎如果Account要保留对象的互斥锁Account,则必须公开.这有点恶臭.
一些建议的解决方案让客户端使用包装类来静默地将互斥锁与Account对象相关联,但是,正如我在我的注释中所指出的,这似乎使代码的不同部分容易使用不同的包装器对象Account,每个都创建自己的互斥,这意味着代码的不同部分可能会尝试锁定Account使用不同的互斥锁.那很糟.
其他提出的解决方案依赖于一次只锁定一个互斥锁.这消除了锁定多个互斥锁的需要,但代价是使某些线程可以看到系统的不一致视图.实质上,这放弃了涉及多个对象的操作的事务语义.
在这一点上,公共互斥体开始看起来像是可用选项中最不臭的,这是我真的不想要的结论.真的没有更好的吗?
在C ++及2012年之后的内容中查看Herb Sutter的演讲:C ++并发。他展示了C ++ 11中类似Monitor Object的实现示例。
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
Run Code Online (Sandbox Code Playgroud)
实现方式:
#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;
}
Run Code Online (Sandbox Code Playgroud)
输出为:
900
1100
Run Code Online (Sandbox Code Playgroud)