为什么会出现死锁?你不能在进程中释放锁吗?

Why does deadlock occur? Can't you release locks mid-process?

假设一个人试图从银行账户 A 向银行账户 B 转账 20 美元,同时另一个人试图从银行账户 B 向银行账户 A 转账 30 美元。为什么这会导致死锁?不能每个线程都执行以下操作:

线程 1 获得A的锁 从 A 中提取 20 美元 释放A的锁 获得B的锁 给 B 加 20 美元 释放 B 的锁

线程 2 获得B的锁 从 B 处提取 30 美元 释放 B 的锁 获得A的锁 给 A 加 30 美元 释放 A 的锁

我知道如果在每个线程结束时释放资源,这会导致死锁。但是,为什么线程不能在资源使用完后立即释放资源锁?

当 2 个资源互相等待释放资源时发生死锁。假设你正穿过一条狭窄的街道,一次只能一个人通过,突然你遇到另一个从相反方向过来的人。现在你们都将等待对方释放资源(这是 street ),直到那时你们都将处于死锁状态。

这里是第一个人必须从账户 A 向 B 转账,他必须获得两个账户才能执行从 A 点到 B 点的交易,账户持有人 1 正在等待账户 B 被释放,但是,账户持有人2 正在等待帐户 A 被释放,如果您可以想象,这将形成无限循环等待条件(或死锁)。

现在考虑您上面的建议,除非事务完成,否则线程 1 不能释放 A 的锁。如果这发生了你说的怎么办!如果在获得 20 美元后,线程 A 的锁被释放并扣除 20 美元,并且由于任何异常而导致账户 B 的交易失败,现在交易将不得不回滚,假设账户 A 被其他线程占用!看到了吗,这可能会导致无限期的等待,进一步的数据不一致的异常。假设用户向 B 发送 20 美元,然后立即停用他的帐户,如果交易失败,钱会去哪里?

给定场景的可能解决方案(发生死锁后):

  1. 没有互斥(意味着没有锁定,什么都没有!你知道的更多,如果你把你的资源(比如肉)留给饥饿的狗会发生什么(threads).你的系统终究会完蛋的(开玩笑,简单的说:这不可能不遵循互斥原则)

  2. 允许抢占 • OS 可以撤销当前所有者的资源

  3. 不要等待 • 等待资源时,当前不得持有任何资源

  4. 按顺序请求资源 • 当等待资源 'x' 时,当前不得持有任何资源 'y' • 如您所见:如果您的程序满足#3,那么它就满足#4

这是我对此的看法。

这是1个线程转账的步骤:

  1. 获取锁A.
  2. 从 A 处提取 20 美元
  3. 获取锁B.
  4. 给 B 加 $20。
  5. 释放锁B.
  6. 释放锁A.

那么,为什么从A取出$20之后不释放锁A呢? 转账必须是交易。这意味着它只有在完成上述 6 个步骤后才标记为成功。如果出现问题,它必须回滚所有内容。

让我们想象一下,在第 4 步 Add to B,由于某种原因,它失败了。这使得线程回滚,add back to A。届时,如果lock A被其他线程占用,会导致无限期等待等问题。

这就是为什么它必须持有锁 A 直到事务完成。

Suppose one person is trying to transfer from bank account A to bank account B and another person is trying to transfer from bank account B to bank account A at the same time. Why should this result in deadlock?

不会。真正的银行业不是这样运作的。

您大概是通过某种类比或证明该概念的简化版本进行推理。

Thread 1 Acquire A's lock Withdraw from A Release A's lock Acquire B's lock Add to B Release B's lock

像这样的事情确实可以用真实的代码来完成。

重要的是要注意,如果由于某种原因我们无法获得锁 B,我们需要再次获得锁 A 以 return 20 美元,因为如果我们不能 return失败情况下的 20 美元 我们没有完全成功或完全失败的交易,并且 20 美元可以消失。因此,如果有可能在获取锁 B 失败后我们可能再次获取锁 A 失败,这是不可接受的。如果某些东西可能先获得 A 的锁,然后获得 B 的锁,并根据它们保护的值做出决定,这也是不可接受的;此时它们的总和不正确。

另一种可能的方法是订购锁。如果 A 总是在 B 之前出现,那么无论以何种方式转移资金,两个线程都将始终尝试在锁 B 之前获取锁 A,并且永远不会发生死锁。一个重要的警告是,如果你有锁 B 并意识到你还需要锁 A,那么你必须在获得锁 A 之前释放锁 B。

另一种可能的方法是在死锁时一个事务丢失,它的工作被撤销并且它的锁可能在重试之前释放,或者可能有异常(或者可能在异常之前重试一定次数)。这在数据库锁定中很常见。请注意,这需要一些控制代码来了解已完成的工作,以便可以撤消。事务事务数据库是这样,但大多数多线程程序不是这样。