![GNU/Linux flock 命令:如何原子地获取多个锁](https://linux22.com/image/1546818/GNU%2FLinux%20flock%20%E5%91%BD%E4%BB%A4%EF%BC%9A%E5%A6%82%E4%BD%95%E5%8E%9F%E5%AD%90%E5%9C%B0%E8%8E%B7%E5%8F%96%E5%A4%9A%E4%B8%AA%E9%94%81.png)
我有几个命令想要并行运行,但前提是它们访问的资源没有冲突。所以我决定使用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)
(显然,需要一个额外的案例。)
在更一般的情况下,锁文件将被传递给一个函数,该函数将文件保存在数组中并构建运行字符串(flock
s 的连续),并使用参数算法来选择在非阻塞锁失败时要排队的文件。
这两者的编码都会很复杂,特别是在允许Command
及其参数中嵌入空间时,所以我选择了上面的简单情况来说明原理,这在一般情况的高级编码中会丢失。