在 Ruby 中安全唤醒线程

Safely wake thread in Ruby

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

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() 时,第一个线程应该被唤醒并且 return 发送的值通过@response.

这应该适用于所有情况,除非是在第一个线程停止之前第二个线程调用关闭并且在第一个线程停止之前存在上下文切换的极少数情况。我怎样才能消除这种(被认为不太可能)的可能性?

与线程通信的最简单方法是使用 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://whosebug.com"
@queue.enq "http://meta.whosebug.com"