在ruby中阻塞队列实现

时间:2014-07-22 16:32:53

标签: ruby multithreading queue

在Java中,有一个名为ArrayBlockingQueue的类作为其并发包的一部分。它是一个线程安全的类,您可以在其中添加和删除队列中的项目,而不必担心线程安全。此类有一个put方法,允许您将项目放入队列。并且take方法从队列中删除项目。关于puttake的两件好事是不需要synchronized关键字来进行线程交错的同步,并且take耐心地等待直到某些东西被添加到队列中,而不是抛出一个如果没有任何内容则例外。

我尝试在ruby中实现类似的东西,但问题是queue.pop似乎阻止了将项目添加到队列中(至少对于其中一个队列),如下所示:

require 'redis'
require 'date'

def log_debug(str)
  debug_str = "#{DateTime.now}  #{str}"
    puts debug_str
end

class EmailsmsResponder
  def initialize
    @queue = Queue.new
  end

  # add to queue
  def produce(channel, msg)
    @queue << {channel: channel, msg: msg}
    puts "queue size: #{@queue.size}"
  end

  # take from queue
  def consume
    loop do
      log_debug "Whats going on??"
      sleep(1)
        if !@queue.empty?
          item = @queue.pop
          log_debug "removing channel #{item[:channel]} and msg #{item[:msg]} from email-sms thread from queue"
        end
    end
  end
end

class SidekiqResponder

  def initialize
    @queue = Queue.new
  end

  def produce(channel, msg)
    @queue << {channel: channel, msg: msg}
    puts "queue size: #{@queue.size}"
  end

  def consume
    loop do
      log_debug "Whats going on??"
      sleep(1)
      if !@queue.empty?
        value = @queue.pop
        log_debug "removing channel #{item[:channel]} and msg #{item[:msg]} from sidekiq thread from queue"
      end
    end
  end
end

class RedisResponder
  def initialize(host,port)
    @host = host
    @port = port
    @email_sms = EmailsmsResponder.new
    @sidekiq = SidekiqResponder.new
    # timeout so we wait for messages forever
    @redis = Redis.new(:host => @host, :port => @port, :timeout => 0)
  end

  def start_producers
    thread = Thread.new do
      @redis.subscribe('juggernaut') do |on|
        # message block fired for new messages
        on.message do |channel, msg|
          log_debug "New message"
          @email_sms.produce(channel, msg)
          @sidekiq.produce(channel, msg)
        end
      end
    end
  end

  def start_consumers
    thread = Thread.new do
      @email_sms.consume
      @sidekiq.consume
    end
  end
end

responder = RedisResponder.new('127.0.0.1', 6379)
responder.start_producers.join(responder.start_consumers.join)

当一个队列似乎正常工作时,另一个队列永远不会检索任何内容:

$ ruby redis-client4.rb
2014-07-22T14:53:24-04:00  Whats going on??
2014-07-22T14:53:25-04:00  Whats going on??
2014-07-22T14:53:25-04:00  New message
queue size: 1
queue size: 1
2014-07-22T14:53:26-04:00  removing channel juggernaut and msg {"channels":["/reports/6561/new"],"data":"New reports for unit 6561"} from email-sms thread from queue
2014-07-22T14:53:26-04:00  Whats going on??
2014-07-22T14:53:27-04:00  Whats going on??
2014-07-22T14:53:28-04:00  Whats going on??
2014-07-22T14:53:28-04:00  New message
queue size: 1
queue size: 2
2014-07-22T14:53:29-04:00  removing channel juggernaut and msg {"channels":["/reports/6561/new"],"data":"New reports for unit 6561"} from email-sms thread from queue
2014-07-22T14:53:29-04:00  Whats going on??
2014-07-22T14:53:30-04:00  Whats going on??
2014-07-22T14:53:31-04:00  Whats going on??
2014-07-22T14:53:31-04:00  New message
queue size: 1
queue size: 3
2014-07-22T14:53:32-04:00  removing channel juggernaut and msg {"channels":["/reports/6561/new"],"data":"New reports for unit 6561"} from email-sms thread from queue
2014-07-22T14:53:32-04:00  Whats going on??
2014-07-22T14:53:33-04:00  Whats going on??
2014-07-22T14:53:34-04:00  Whats going on??
2014-07-22T14:53:34-04:00  New message
queue size: 1
queue size: 4
2014-07-22T14:53:35-04:00  removing channel juggernaut and msg {"channels":["/reports/6561/new"],"data":"New reports for unit 6561"} from email-sms thread from queue
2014-07-22T14:53:35-04:00  Whats going on??
2014-07-22T14:53:36-04:00  Whats going on??
2014-07-22T14:53:37-04:00  Whats going on??
2014-07-22T14:53:37-04:00  New message
queue size: 1
queue size: 5

我可能做错了什么?

2 个答案:

答案 0 :(得分:0)

  

我尝试在ruby中实现类似的东西,但问题是   即使将项目添加到队列

,queue.pop似乎也会阻止

这很容易被反驳:

require 'thread'

q  = Queue.new
q << 'hello'

x = q.pop
puts x

x = q.pop

--output:--
hello

deadlock detected (fatal)
  

我可能做错了什么?

开始删除代码并简化操作以查明问题发生的位置。事实上,你有两个完全相同的课程意味着你甚至没有开始简化。

然后有这个:

  def consume
    loop do
      log_debug "Whats going on??"
      sleep(1)
      value = queue.pop
      log_debug "removing channel: #{channel} msg: #{msg} of sidekiq thread from queue"
    end
  end

 ***Error in `consume': undefined local variable or method `queue'

答案 1 :(得分:0)

我使用下面的代码。我不喜欢我必须使用4个线程才能让它工作,所以如果有人有更好的解决方案,我很乐意推荐他们的解决方案。但这似乎现在正在起作用:

require 'redis'
require 'date'

def log_debug(str)
  debug_str = "#{DateTime.now}  #{str}"
    puts debug_str
end

class EmailsmsResponder
  def initialize
    @queue = Queue.new
  end

  # add to queue
  def produce(channel, msg)
    @queue << {channel: channel, msg: msg}
    puts "queue size: #{@queue.size}"
  end

  # take from queue
  def consume
    loop do
      log_debug "Whats going on??"
      sleep(1)
        if !@queue.empty?
          item = @queue.pop
          log_debug "removing channel #{item[:channel]} and msg #{item[:msg]} from email-sms thread from queue"
        end
    end
  end
end

class SidekiqResponder

  def initialize
    @queue = Queue.new
  end

  def produce(channel, msg)
    @queue << {channel: channel, msg: msg}
    puts "queue size: #{@queue.size}"
  end

  def consume
    loop do
      log_debug "Whats going on??"
      sleep(1)
      if !@queue.empty?
        item = @queue.pop
        log_debug "removing channel #{item[:channel]} and msg #{item[:msg]} from sidekiq thread from queue"
      end
    end
  end
end

class RedisResponder
  def initialize(host,port)
    @host = host
    @port = port
    @email_sms = EmailsmsResponder.new
    @sidekiq = SidekiqResponder.new
    # timeout so we wait for messages forever
    @redis = Redis.new(:host => @host, :port => @port, :timeout => 0)
  end

  def start_producers
    thread = Thread.new do
      @redis.subscribe('juggernaut') do |on|
        # message block fired for new messages
        on.message do |channel, msg|
          log_debug "New message"
          @email_sms.produce(channel, msg)
          @sidekiq.produce(channel, msg)
        end
      end
    end
  end

  def start_consumers
    thread = Thread.new do
      t1 = Thread.new { @email_sms.consume }
      t2 = Thread.new { @sidekiq.consume }
      t1.join(t2.join)
    end
  end
end

responder = RedisResponder.new('127.0.0.1', 6379)
responder.start_producers.join(responder.start_consumers.join)