GNU/Linux flock 命令:如何原子地获取多个锁

GNU/Linux flock 命令:如何原子地获取多个锁

我有几个命令想要并行运行,但前提是它们访问的资源没有冲突。所以我决定使用flock。每个命令都必须采用一个独占(写)锁和几个共享(读)锁。由于flock不支持多个锁,我天真地认为类似这样的事情:

flock -x a flock -s b flock -s c ... <command>

可以工作。我很快发现这种方法存在竞争条件,因为锁集不是原子的。启动时:

flock -x a flock -s b <command1> &
flock -x b flock -s a <command2> &

可能会同时获取两个排他锁,并且两个命令由于无法获取共享锁而进入死锁。

有解决方法吗?是否有其他以原子方式支持多个锁的锁定实用程序?或者我应该创建自己的一个,尝试获取锁,如果失败则在超时后释放所有锁,然后在随机延迟后重试?或者类似的东西?

更新显然,按名称对锁进行排序可以解决问题:

flock -x a flock -s b <command1> &
flock -s a flock -x b <command2> &

但是这有多强大?它是否可以在所有情况下避免死锁,无论命令数量、锁数量、锁名称和每个命令的锁集是多少(仍然有每个命令只有一个独占锁的限制)?

答案1

这是哲学家就餐问题. 通过对您实施的锁进行排序资源层级解决方案

虽然资源层次结构解决方案避免了死锁,但它并不总是实用的,尤其是在事先无法完全了解所需资源列表的情况下。

只要你能够整理好自己的资源并坚持下去,它看起来就会很强大。


一种解决方法可能是不要flock无限期地等待,然后添加一些逻辑来检测退出的情况,因为它无法锁定文件,例如在随机时间之后重复整个任务。

man flock有人能看见:

-n,,如果无法立即获取锁,则失败(退出代码为)而不是--nb等待--nonblock
1

-w, --wait,如果在 seconds 秒内无法获取锁,则--timeout seconds
失败(退出代码为)。允许使用十进制小数值。1

问题是:可能的退出代码1可能来自任何flock命令或底层命令。如果您flock支持-E指定自定义退出代码——可以使用它。

这是该方法的一个简单示例:

while ! flock -n -x file <command> ; do sleep $(($RANDOM%5)) ; done

您可以使用多个flock-s。如果其中任何一个无法锁定文件,则释放所有锁定,并且整行在 处等待sleep,而不是在 处flock;此时它不会阻止并行执行的另一行类似操作。

答案2

当我参与实时编程时,我总是厌恶延迟/重试解决方案,尽管这些解决方案通常更容易编码。

避免死锁的关键是永远不要在持有一个锁时排队等待第二个锁。因此,对于三个文件,请使用类似以下方法:-

while true
do  flock -x a flock -nE 101 -s b flock -nE 102 c Command
    case $? in
    101) flock -s b;;
    102) flock -s c;;
    *)   break;;
done

使用的返回值flock -E必须是命令永远不会返回的值,当返回其中一个值时,脚本将对锁定的资源进行排队,然后重复原始调用。

原则上,请求锁的顺序并不重要,但首先请求独占锁可能会简化编码。

有一个更有效的解决方案,可以避免在再次请求之前立即释放排队锁:每次构建运行字符串,在每次非阻塞故障时重建它,例如对于 101 返回,运行字符串将变成:

flock -s b flock -nE 102 flock -nE 100 -x a c Command

100)(显然,需要一个额外的案例。)

在更一般的情况下,锁文件将被传递给一个函数,该函数将文件保存在数组中并构建运行字符串(flocks 的连续),并使用参数算法来选择在非阻塞锁失败时要排队的文件。

这两者的编码都会很复杂,特别是在允许Command及其参数中嵌入空间时,所以我选择了上面的简单情况来说明原理,这在一般情况的高级编码中会丢失。

相关内容