安全地在Ruby中唤醒线程

时间:2016-03-01 16:10:12

标签: ruby multithreading concurrency mutex

我需要有一个线程来阻止自己然后被另一个线程唤醒。我遇到的问题是我无法找到一个完全万无一失的好解决方案。我现在的代码看起来像这样:

def initialize
  @data_lock = Mutex.new
  @closed = false
end

def get_response
  @data_lock.synchronize do
    @blocked_thread = Thread.current
  end
  # This loop is a safe guard against accidental wakeup of thread
  loop do
    @data_lock.synchronize do
      if @closed
        return @response
      end
    end
    # FIXME: If context switch happens here the thread will be permanently frozen.
    Thread.stop  # Stop current thread and wait for call to close()
  end
end


def close(response)
  @data_lock.synchronize do
    @closed = true
    @response = response
    Thread.pass  # An attempt at minimizing the risk of permanently freezing threads
    if @blocked_thread.is_a? Thread and @blocked_thread.status == 'sleep'
      @blocked_thread.wakeup
    end
  end
end

它应该工作的方式是调用get_response将阻塞当前线程,当另一个线程调用close()时,第一个线程应该被唤醒并返回通过@response发送的值。

这应该适用于所有情况,除非在第一个线程停止之前第二个线程调用close并且在第一个线程停止之前有一个上下文切换的极少数情况下。我怎样才能删除这个(授予非常不可能)的可能性?

1 个答案:

答案 0 :(得分:0)

与线程通信的最简单方法是使用Thread#Queue对象。 Thread#Queue是一个线程安全的FIFO队列。

require "thread"

@queue = Queue.new

当胎面想要阻止直到发出信号时,它会从队列中读取。队列为空时线程将停止:

@queue.deq

要唤醒线程,请在队列中写入内容:

@queue.enq :wakeup

在这里,我们只是将一个符号扔进了队列。但是您也可以将内容写入您希望线程处理的队列中。例如,如果一个线程正在处理URL,它可以从队列中检索它们:

loop do
  url = @queue.deq
  # process the url
end

其他一些线程可以将URL添加到队列中:

@queue.enq "http://stackoverflow.com"
@queue.enq "http://meta.stackoverflow.com"