使用File#flock作为Ruby全局锁(进程的互斥锁)

10

在进行了短暂的研究后,我发现两个进程之间存在并发问题,临时文件被建议作为此问题的解决方案。

因此,解决方案是创建/tmp/global.lock并将其用作全局锁。 我在这个线程中找到了一个示例:Mutex for Rails Processes

到目前为止,对我来说是有道理的,但我想看看这种解决方案的最佳实践。上面的解释是有道理的,但我想知道如何检查给定的文件是否已锁定?

fh = File.open("/some/file/path", File::CREAT)

begin
  if locked = check_file_locked?
    sleep(1)
  else
    fh.flock(File::LOCK_EX)
    # do what you need to do
  end
ensure
  fh.flock(File::LOCK_UN)
end

这是我对解决方案的理解,不确定如何实现提到的check_file_locked?()函数?如果有更好的方法,请告诉我。
3个回答

11
@bjhaid的回答可能会导致Timeout#timeout在Rubinius解释器中出现错误。而且这个方法过于复杂。
以下是一个更简单的版本,使用非阻塞锁(nonblocking lock)来代替timeout:
def locked? lockfile_name
  f = File.open(lockfile_name, File::CREAT)

  # returns false if already locked, 0 if not
  ret = f.flock(File::LOCK_EX|File::LOCK_NB)

  # unlocks if possible, for cleanup; this is a noop if lock not acquired
  f.flock(File::LOCK_UN) 

  f.close
  !ret # ret == false means we *couldn't* get a lock, i.e. it was locked
end

4
一旦你关闭文件,锁将被释放。因此,如果您使用open的块语法(例如open(path) do |f|...end),则可以同时删除LOCK_UN操作和显式关闭。此外,如果使用block from,则即使发生异常,锁也会被释放。 - hagello

8
当您对文件拥有独占锁时,在Ruby中尝试再次锁定该文件将无限期地等待,直到文件解锁,因此您可以依赖于它并设置超时来确定Ruby应等待多长时间,这可能不是最合适的方法,但我会按以下方式处理:
fh = File.open("/some/file/path", File::CREAT)
fh.flock(File::LOCK_EX)

require 'timeout'
def check_file_locked?(file)
  f = File.open(file, File::CREAT)
  Timeout::timeout(0.001) { f.flock(File::LOCK_EX) }
  f.flock(File::LOCK_UN)
  false
rescue 
  true
ensure
  f.close
end
f = File.open("/tmp/a.txt", "w+")
f.flock(File::LOCK_EX)
check_file_locked?("/tmp/a.txt") # => true
f.flock(File::LOCK_UN)
check_file_locked?("/tmp/a.txt") # => false 

3
这是对于一个魔数(0.001)的不良使用。为什么恰好采用这个值?为什么不能更大或更小?这个任意常数应该至少被解释一下。 - hagello
3
以非阻塞方式获取锁的正确接口是File::LOCK_NB。您需要将其与所需操作进行逻辑或运算。例如:flock(File::LOCK_EX | File::LOCK_NB)。避免使用变通方法!此外,使用LOCK_NB比使用超时的任何方法都要快得多。 - hagello
这个神奇的数字正在睡眠中。解释或更糟糕的是添加一个常量会增加更多的心理模型,使代码更难以阅读。欢迎来到现实世界。代码的真正问题在于,“timeout”是Ruby中实现得非常糟糕的功能,应尽可能避免使用。 - Lothar

1

我为此编写了一个简单的Mutex类

class CrossProcessMutex
  def initialize(lock_filepath)
    @lock_filepath = lock_filepath
  end

  def synchronize
    # https://ruby-doc.org/3.2.0/File.html#method-i-flock
    f = File.open(@lock_filepath, File::RDWR|File::CREAT, 0644)
    f.flock File::LOCK_EX
    yield
  ensure
    f.flock File::LOCK_UN
  end
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接