在将钱从一个银行账户转移到另一个银行账户的经典问题中,公认的解决方案(我相信)是将互斥体与每个银行账户相关联,然后在从一个账户提取资金并将其存入另一个账户之前锁定两者.乍一看,我会这样做:
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();
}
但是手动解锁有味道。我可以公开互斥量,然后在 transfer
中使用 std::lock_guard
,但公共(public)数据成员也有异味。
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);
}
这似乎没问题,但我以前从未见过这种事情,并且在 Account
中复制互斥锁的锁定和解锁本身似乎有点臭。
在这种情况下,将互斥锁与其保护的数据相关联的最佳方式是什么?
更新:在下面的评论中,我注意到 std::lock
可用于避免死锁,但我忽略了 std::lock
依赖于try_lock
功能(除了 lock
和 unlock
的功能)。将 try_lock
添加到 Account
的界面似乎是一个相当粗暴的 hack。因此,如果 Account
对象的互斥锁要保留在 Account
中,它就必须是公开的。里面有一股恶臭。
一些提议的解决方案让客户使用包装类静默地将互斥锁与 Account
对象相关联,但是,正如我在评论中指出的那样,这似乎使代码的不同部分变得容易在 Account
周围使用不同的包装器对象,每个都创建自己的互斥锁,这意味着代码的不同部分可能会尝试使用不同的互斥锁锁定 Account
。这很糟糕。
其他提议的解决方案依赖于一次只锁定一个互斥锁。这消除了锁定多个互斥锁的需要,但代价是某些线程可能会看到不一致的系统 View 。本质上,这放弃了涉及多个对象的操作的事务语义。
在这一点上,公共(public)互斥开始看起来像是可用选项中最不臭的,这是我真的不想得出的结论。真的没有更好的了吗?
最佳答案
查看 Herb Sutter 在 C++ and Beyond 2012 上的演讲:C++ Concurrency .他展示了 Monitor Object 的例子-类似于 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
实现:
#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;
}
输出是:
900
1100
关于c++ - 将互斥锁与其数据相关联的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15844972/