将互斥锁与其数据相关联的正确方法是什么?

Kno*_*abe 10 c++ mutex c++11

在将钱从一个银行账户转移到另一个银行账户的经典问题中,接受的解决方案(我相信)是将互斥锁与每个银行账户相关联,然后在从一个账户中提取资金并将其存入另一个账户之前锁定两者.乍一看,我这样做:

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_guardtransfer,但公共数据成员闻到了.

要求std::lock_guard是它的类型满足BasicLockable要求,这只是调用lock并且unlock有效.Account满足这一要求,所以我可以只使用std::lock_guardAccount直接:

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功能(除了对于lockunlock).添加try_lockAccount界面似乎是一个相当严重的黑客.因此,似乎如果Account要保留对象的互斥锁Account,则必须公开.这有点恶臭.

一些建议的解决方案让客户端使用包装类来静默地将互斥锁与Account对象相关联,但是,正如我在我的注释中所指出的,这似乎使代码的不同部分容易使用不同的包装器对象Account,每个都创建自己的互斥,这意味着代码的不同部分可能会尝试锁定Account使用不同的互斥锁.那很糟.

其他提出的解决方案依赖于一次只锁定一个互斥锁.这消除了锁定多个互斥锁的需要,但代价是使某些线程可以看到系统的不一致视图.实质上,这放弃了涉及多个对象的操作的事务语义.

在这一点上,公共互斥体开始看起来像是可用选项中最不臭的,这是我真的不想要的结论.真的没有更好的吗?

Evg*_*yuk 5

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)