同时锁定多个对象

时间:2016-05-18 15:51:02

标签: c# concurrency locking

在我的代码中,我有一种处理将钱从一个帐户转移到另一个帐户的方法。显然,这种方法必须确保在程序执行的任何给定时刻只针对每个帐户执行一个事务。我使用2个嵌套锁实现了此检查(Account是一个数据库实体,具有唯一的整数ID):

public void Transfer(Account from, Account to, decimal amount)
{
    lock(GetLockObject(from))
    {
        lock(GetLockObject(to))
        {
            DoTransfer(from, to, amount);
        }
    }
}

private static Dictionary<int, object> lockObjects = new Dictionary<int, object>();
private static GetLockObject(Accont acc)
{
    lock (lockObjects)
    {
        var lockObject = lockObjects[acc.Id];
        if (lockObject == null)
        {
            lockObject = new object();
            lockObjects[accId] = lockObject;
        }
        return lockObject;
    }
}

这似乎工作正常,但我有两个问题:

  • 如果使用与其参数相同的2个帐户同时调用该方法,代码可能会导致死锁:Transfer(acc1, acc2, 10); Transfer(acc2, acc1, 10);如何避免死锁?
  • 与使用嵌套锁相比,能否以更好的方式(在性能,可靠性等方面)实现相同的结果?

1 个答案:

答案 0 :(得分:1)

关于你的第一点:介绍一些帐户的排序(例如id),首先锁定一个id较小的id,一个id较大的一个。

至于你的第二个问题,有选择。例如,如果您确定需要锁定to帐户,并且当balance(from) + balance(to)小于输入Transfer方法时可以容忍一个小的不一致窗口(较小)通过amount),您可以这样做:

public void Transfer(Account from, Account to, decimal amount) {
    bool withdrawalSuccessful = false;
    lock(GetLockObject(from)) {
        withdrawalSuccessful = Withdraw(from, amount);
    }
    if (withdrawalSuccessful) {
        lock(GetLockObject(to)) {
            Deposit(to, amount)
        }
    }
}

或者你可以完全摆脱锁定“to”:不是将帐户余额表示为一个数字,而是存储“存款”和“提款”的集合(无论是普通列表还是数据库表中的记录或其他)。然后你可能只是添加存款到“to”而不锁定。

或者说它不是“存款”和“取款”,而只是一些金额连同所有者信息和一些ID。然后,为了转移,你:

  1. 锁定“来自”
  2. 检查属于from的元组的金额是>= amount
  3. 拆分它的一些元组,说(id0, M, from)其中M >= amount(如果没有这样的元组然后首先合并一些现有元组)到[(id1, amount, from), (id2, M-amount, from)],然后自动更改{{} 1}}((id1, amount, from) -> (id1, amount, to)或仅"UPDATE money_tuples SET owner='<to>' WHERE id='<id1>'"),
  4. 终于解锁“来自”。
  5. 与前一个选项的不同之处在于您使用(“atomic”)“UPDATE”或变量赋值,而不是将两个对象添加到单独的集合中。