在将钱从一个银行帐户转移到另一个帐户的经典问题中,公认的解决方案(我相信)是将互斥锁与每个银行帐户相关联,然后在从一个帐户提取资金并将其存入另一个帐户之前锁定两者。乍一看,我会这样做:
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();
}
但是手动解锁有异味。我可以公开互斥体,然后使用std::lock_guard
in transfer
,但公共数据成员也有味道。
的要求std::lock_guard
是它的类型满足BasicLockablelock
的要求,也就是调用unlock
有效。Account
满足该要求,因此我可以直接使用std::lock_guard
with 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
功能的存在(除了 forlock
和unlock
)。添加try_lock
到Account
的界面似乎是一个相当严重的黑客攻击。因此,如果Account
要保留对象的互斥锁Account
,它似乎必须是公共的。这有相当的恶臭。
一些提议的解决方案让客户使用包装器类将互斥锁与Account
对象静默关联,但是,正如我在评论中指出的那样,这似乎使代码的不同部分可以轻松使用不同的包装器对象Account
,每个都创建自己的互斥体,这意味着代码的不同部分可能会尝试Account
使用不同的互斥体来锁定。那很糟。
其他提议的解决方案依赖于一次仅锁定一个互斥锁。这消除了锁定多个互斥体的需要,但代价是某些线程可能看到系统的不一致视图。从本质上讲,这放弃了涉及多个对象的操作的事务语义。
在这一点上,公共互斥锁开始看起来像是可用选项中最不臭的,这是我真的不想得出的结论。真的没有更好的了吗?