【问题标题】:How do I ensure only one instance of a Ruby script is running at a time?如何确保一次只运行一个 Ruby 脚本实例?
【发布时间】:2020-06-25 01:44:34
【问题描述】:

我有一个进程每五分钟在 cron 上运行一次。通常,运行只需几秒钟,但有时需要几分钟。我想确保一次只运行一个版本。

我尝试了一个明显的方法......

File.open("/tmp/indexer_lock.tmp",'w') do |f|
  exit unless f.flock(File::LOCK_EX)
end

...但它不是测试是否可以获取锁,它会阻塞直到锁被释放。

知道我错过了什么吗?我宁愿不使用 ps 破解某些东西,但这是另一种选择。

【问题讨论】:

    标签: ruby linux


    【解决方案1】:

    我知道这是旧的,但对于任何感兴趣的人,有一个非阻塞常量可以传递给flock,以便它返回而不是阻塞。

    File.new("/tmp/foo.lock").flock( File::LOCK_NB | File::LOCK_EX )
    

    更新slhck

    flock 如果 this 进程收到锁将返回 true,否则返回 false。因此,为了确保一次只运行一个进程,您只想尝试获得锁,如果无法获得则退出。就像在我上面的代码行前面放一个exit unless 一样简单:

    exit unless File.new("/tmp/foo.lock").flock( File::LOCK_NB | File::LOCK_EX )
    

    【讨论】:

    • 您能否用一个最小的工作示例扩展这个答案?我无法理解如何使用那段代码。那太好了!
    • 在某些机器上可能需要传递创建/打开文件的权限,例如linux。
    【解决方案2】:

    根据您的需要,这应该可以正常工作,并且不需要在任何地方创建另一个文件。

    exit unless DATA.flock(File::LOCK_NB | File::LOCK_EX)
    
    # your script here
    
    __END__
    DO NOT REMOVE: required for the DATA object above.
    

    【讨论】:

    • 这非常漂亮,而且似乎运行良好。现在花点时间了解它到底在做什么。
    • 请注意,DATA 常量仅适用于第一个执行的 Ruby 脚本,因此 bundle exec 将打破这一点,因为 DATA 将设置在捆绑脚本上,而不是您的脚本正在捆绑执行。
    【解决方案3】:

    虽然这不是直接回答你的问题,但如果我是你,我可能会编写一个守护程序脚本(你可以使用http://daemons.rubyforge.org/

    您可以让您的索引器(假设它的 indexer.rb)通过名为 script/index 的包装器脚本运行,例如:

    require 'rubygems'
    require 'daemons'
    
    Daemons.run('indexer.rb')
    

    你的索引器几乎可以做同样的事情,除了你指定一个睡眠间隔

    loop do
       # code executing your indexing 
    
       sleep INDEXING_INTERVAL
    end
    

    这就是作业处理器与队列服务器协同工作的通常方式。

    【讨论】:

      【解决方案4】:

      您可以创建和删除一个临时文件并检查该文件是否存在。 请检查这个问题的答案: one instance shell script

      【讨论】:

        【解决方案5】:

        lockfile gem 正是针对这种情况。我以前用过,很简单。

        【讨论】:

        • 这太棒了。很简单:Lockfile("name_of_lock_file", :retries => 0) { your_logic_here }
        • 这里是最新版本,兼容 Ruby 1.9:github.com/ahoward/lockfile
        【解决方案6】:

        如果您使用 cron,在 cron 调用的 shell 脚本中执行类似这样的操作可能会更容易:

        #!/usr/local/bin/bash
        #
        
        if ps -C $PROGRAM_NAME &> /dev/null ; then
           : #Program is already running.. appropriate action can be performed here (kill it?)
        else
           #Program is not running.. launch it.
           $PROGRAM_NAME
        fi
        

        【讨论】:

          【解决方案7】:

          这是一个适用于任何 Ruby 脚本顶部的单行代码:

          exit unless File.new(__FILE__)).tap {|f| f.autoclose = false}.flock(File::LOCK_NB | File::LOCK_EX)
          

          原始代码有两个问题。

          首先,它被阻塞的原因是对#flock的调用丢失了File::LOCK_NB

          锁定时不要阻塞。可以合并 与其他使用逻辑或的锁定选项。

          第二,如果 File 对象被关闭(无论是在上面代码中的 #open 块的末尾,通过显式 #close,还是在 File 关闭时隐式自动关闭)垃圾收集),底层文件描述符被关闭并释放锁。为了防止这种情况,您可以设置#autoclose =false

          【讨论】:

            【解决方案8】:

            好的,根据@shodanex 的指针处理笔记,这就是我所拥有的。我稍微改进了一下(虽然我不知道 Ruby 中有触摸模拟)。

            tmp_file = File.expand_path(File.dirname(__FILE__)) +  "/indexer.lock"
            if File.exists?(tmp_file)
              puts "quitting"
              exit
            else
              `touch #{tmp_file}`
            end
            
            .. do stuff ..
            
            File.delete(tmp_file)
            

            【讨论】:

            • 与其创建新答案,不如编辑您的原始问题以显示您如何使用真实答案来解决问题。更多的人会在那里看到它。
            【解决方案9】:

            你能不能把 File::LOCK_NB 添加到你的锁中,使它成为非阻塞的(即如果它不能得到锁,它会失败)

            这适用于 C、Perl 等。

            【讨论】:

              【解决方案10】:

              在更高层次上,您可能会发现 lock_method gem 很有用:

              def the_method_my_cron_job_calls
                # something really expensive
              end
              lock_method :the_method_my_cron_job_calls
              

              它默认使用存储在本地文件系统上的锁文件(上面已经讨论过),但您也可以配置远程锁存储:

              LockMethod.config.storage = Redis.new([...]) # a remote RedisToGo instance, perhaps?
              

              还有……

              def the_method_my_cron_job_calls
                # something really expensive
              end
              lock_method :the_method_my_cron_job_calls, (60*60) # automatically expire lock after an hour
              

              【讨论】:

                猜你喜欢
                • 2011-09-22
                • 2012-01-08
                • 2010-09-16
                • 2011-04-27
                • 1970-01-01
                • 1970-01-01
                • 2012-01-09
                • 2011-01-15
                • 2019-02-08
                相关资源
                最近更新 更多