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

标签 c++ c++11 mutex

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

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 要求,也就是调用 lockunlock 有效。 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);
}

这似乎没问题,但我以前从未见过这种事情,并且在 Account 中复制互斥锁的锁定和解锁本身似乎有点臭。

在这种情况下,将互斥锁与其保护的数据相关联的最佳方式是什么?

更新:在下面的评论中,我注意到 std::lock 可用于避免死锁,但我忽略了 std::lock 依赖于try_lock 功能(除了 lockunlock 的功能)。将 try_lock 添加到 Account 的界面似乎是一个相当粗暴的 hack。因此,如果 Account 对象的互斥锁要保留在 Account 中,它就必须是公开的。里面有一股恶臭。

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

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

在这一点上,公共(public)互斥开始看起来像是可用选项中最不臭的,这是我真的不想得出的结论。真的没有更好的了吗?

最佳答案

查看 Herb SutterC++ 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

实现:

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;
}

输出是:

900
1100

关于c++ - 将互斥锁与其数据相关联的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15844972/

相关文章:

c++ - 如何防止使用 CPropertyPage::OnOk() 关闭 MFC 对话框的窗口?

c++ - (c/c++) 字符串文字的拷贝是否在 TEXT 部分共享内存?

c++ - 在连续内存中存储任意元素

c++ - 用作成员构造函数参数的函数的求值顺序

c - 为什么第二个函数没有运行和/或产生输出? (Linux 中的 C)

c++ - 与 std::lock_guard 相比,std::scoped_lock 的目的是否仅用于处理多个互斥量?

c++ - OpenCV 无法在自定义目录中编译

c++ - 为什么不使用 "virtual"关键字动态绑定(bind)就不能工作?

c++ - 在编译时计算第 n 个素数

c++ - 对象在析构函数中获取互斥量以防止删除自身