如何使用 File#flock 对独占锁发出非阻塞请求?

Posted

技术标签:

【中文标题】如何使用 File#flock 对独占锁发出非阻塞请求?【英文标题】:How can I make a non-blocking request for an exclusive lock using File#flock? 【发布时间】:2013-02-24 15:17:36 【问题描述】:

我应该如何申请非阻塞锁?

当单独尝试锁定文件时,为什么 Ruby 的 File#flock 不能按预期工作?将文件锁定在块中并不是解决此问题的正确解决方案,因为重点是显示锁定 持久 锁的行为。在块内使用 File#flock 会在块退出时释放锁,因此它不能正确演示问题。

File#flock 以多种方式失败,尤其是在请求非阻塞锁时。下面是一些例子。

File#flock 的失败示例

使用多个独占锁时无限等待,因为#flock 不提供超时锁请求的方法。

# First lock succeeds.
f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX)
# => 0

# This never returns.
f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
f2.flock(File::LOCK_EX)

在文件被独占锁定时请求非阻塞锁定会导致无效参数异常。

f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX)
# => 0

f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
f2.flock(File::LOCK_NB)
# => Errno::EINVAL: Invalid argument - foo

文档说#flock“根据locking_constant(下表中的逻辑或值)锁定或解锁文件。”但是,根据平台,逻辑 OR 会引发 Errno::EINVALErrno::EBADF

f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX)
# => 0

f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
f2.flock(File::LOCK_NB || File::LOCK_EX)
# => Errno::EINVAL: Invalid argument - foo

本机文件#flock 解决方案首选

虽然在无法获得独占锁时可能会使用Timeout module 引发Timeout::Error,但似乎 File#flock 应该能够原生解决这个问题。那么,实际上应该如何在不阻塞的情况下请求独占锁呢?

【问题讨论】:

【参考方案1】:

使用带排他锁的超时模块

您可以使用Timeout module 设置#flock 获取排他锁的持续时间。下面的示例将引发Timeout::Error: execution expired,然后可以以任何适合应用程序的方式来拯救它。当计时器到期时返回 nil 允许测试 #flock 表达式的真实性。

require 'timeout'

f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX)
# => 0

f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
Timeout::timeout(0.001)  f2.flock(File::LOCK_EX)  rescue nil
# => nil

对非阻塞锁尝试使用按位或

File#flock 的文档说:

根据locking_constant(下表中的逻辑或值)锁定或解锁文件。如果指定了 File::LOCK_NB,则返回 false,否则操作将被阻止。

但是,该方法实际上需要 Bitwise OR 运算符,而不是 tOROP 解析器标记的 defined in parse.y 的逻辑 OR 关键字。因此,当排他锁失败时,允许#flock 返回false 的正确参数实际上是File::LOCK_NB|File::LOCK_EX。例如:

f1 = File.open('foo', File::RDWR|File::CREAT, 0644)
f1.flock(File::LOCK_EX|File::LOCK_NB)
# => 0

f2 = File.open('foo', File::RDWR|File::CREAT, 0644)
f2.flock(File::LOCK_NB|File::LOCK_EX)
# => false

f1.close; f2.close
# => nil

这将在可用时始终生成排他锁;否则,它会立即返回一个虚假值,而无需引发或挽救异常的开销。这显然是该模块的预期使用方式,但文档可能会使用一些说明和附加示例以使其更易于理解。

【讨论】:

以上是关于如何使用 File#flock 对独占锁发出非阻塞请求?的主要内容,如果未能解决你的问题,请参考以下文章

PHP 利用文件锁处理高并发

PHP 利用文件锁处理高并发

关于flock文件锁的阻塞与非阻塞

linux使用flock解决crontab任务冲突

php文件锁解决少量并发问题

php 文件锁