Ruby - 使用Mutex防止线程过早停止

时间:2014-10-18 00:17:40

标签: ruby multithreading mutex

我正在编写一个Ruby应用程序(Linux x86_64中的Ruby v2.1.3p242),它将重复处理在线数据并将结果存储在数据库中。为了加快速度,我有多个并发运行的线程,我一直在努力在命令和从线程引发异常时干净地停止所有线程。

问题是在调用#do_stuff之后,某些线程将继续运行Sever.stop的多次迭代。他们最终会停止,但我会看到一些线程在其余部分停止后运行10-50次。

每个帖子'在每次迭代之前锁定互斥锁并在之后解锁。调用@mutex.synchronize { kill }时,代码Server.stop在每个线程上运行。这个应该在下一次迭代后立即杀死该线程,但似乎并非如此。

修改

代码按原样运行,如果您愿意,可随意测试。在我的测试中,调用Server.stop后所有线程停止需要30秒到几分钟。请注意,每次迭代需要1-3秒。我使用以下代码测试代码(在同一目录中使用ruby -I.):

require 'benchmark'
require 'server'

s = Server.new
s.start
puts Benchmark.measure { s.stop }

以下是代码:

server.rb:

require 'server/fetcher_thread'

class Server
  THREADS = 8  

  attr_reader :threads
  def initialize
    @threads = []
  end

  def start
    create_threads
  end

  def stop
    @threads.map {|t| Thread.new { t.stop } }.each(&:join)
    @threads = []
  end

  private

  def create_threads
    THREADS.times do |i|
      @threads << FetcherThread.new(number: i + 1)
    end
  end
end

服务器/ fetcher_thread.rb:

class Server
  class FetcherThread < Thread
    attr_reader :mutex

    def initialize(opts = {})
      @mutex = Mutex.new
      @number = opts[:number] || 0

      super do      
        loop do
          @mutex.synchronize { do_stuff } 
        end
      end
    end

    def stop
      @mutex.synchronize { kill }
    end

    private

    def do_stuff
      debug "Sleeping for #{time_to_sleep = rand * 2 + 1} seconds"
      sleep time_to_sleep
    end

    def debug(message)
      $stderr.print "Thread ##{@number}: #{message}\n"
    end
  end
end

1 个答案:

答案 0 :(得分:1)

无法保证调用stop的线程在下一次循环迭代之前将获取互斥锁。这完全取决于Ruby和操作系统调度程序,有些操作系统(including Linux)没有实现FIFO调度算法,但还要考虑其他因素来尝试优化性能。

您可以通过避免kill并使用变量干净地退出循环来使其更具可预测性。然后,您只需要在访问变量

的代码周围包含互斥锁
class Server
  class FetcherThread < Thread
    attr_reader :mutex

    def initialize(opts = {})
      @mutex = Mutex.new
      @number = opts[:number] || 0

      super do      
        until stopped?
          do_stuff
        end
      end
    end

    def stop
      mutex.synchronize { @stop = true }
    end

    def stopped?
      mutex.synchronize { @stop }
    end

    #...
  end
end