Ruby Wait / Notify / NotifyAll

rubyWhile Ruby is one of the most productive and expressive languages in which I have programmed, I’ve been wondering lately whether the existence of the GIL has stunted its growth for concurrent programming. Instead of building concurrency into the language, most of the focus in the community has been around working within its limitations and scaling through processes. For example, there is no equivalent in Ruby of the Java concurrency package; the language supports a mutex but not a semaphore (isn’t a mutex just a semaphore with a count of 1?); and there is no construct for inter-thread signaling.

To workaround some of these limitations, I recently adopted JRuby as a means to gain access to the expressiveness of Java for distributed programming. But where possible, I have been trying to maintain MRI compatibility. Below is a simple implementation of the Java wait/notify pattern, which I’ve always thought was an elegant implementation of inter-thread signaling. Feedback is welcome – especially around the use of Thread.exclusive to avoid concurrency issues. Maybe someday we could push a similar extension back into the base Ruby language. ;)

# Old school Java wait/notify

if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
  # Use Java wait/notify/notifyAll
  require "java"

  class Object

    def wait
      create_monitor unless @monitor
      @monitor.synchronized {
        @monitor.wait
      }
    end

    def notify
      create_monitor unless @monitor
      @monitor.synchronized {
        @monitor.notify
      }
    end

    def notify_all
      create_monitor unless @monitor
      @monitor.synchronized {
        @monitor.notify_all
      }
    end

    protected

    def create_monitor
      Thread.exclusive {
        # If the monitor was created by another thread, just exit
        return if @monitor
        @monitor = java.lang.Object.new
      }
    end

  end

else
  # Implement an equivalent in MRI

  class Object
    def wait
      create_monitor unless @monitor
      @monitor.synchronize {
        @waiting_threads << Thread.current
      }
      # Warning: Due to lack of language support in Ruby, the thead stop is outside synchronized block
      Thread.stop
    end
   
    def notify
      create_monitor unless @monitor
      if @monitor and @waiting_threads
        @monitor.synchronize {
          @waiting_threads.delete_at(0).run unless @waiting_threads.empty?
        }
      end
    end
   
    def notify_all
      create_monitor unless @monitor
      if @monitor and @waiting_threads
        @monitor.synchronize {
          @waiting_threads.each {|thread| thread.run}
          @waiting_threads = []
        }
      end
    end

    protected

    def create_monitor
      Thread.exclusive {
        # If the monitor was created by another thread, just exit
        return if @monitor
        @waiting_threads = [] unless @waiting_threads
        @monitor = Mutex.new unless @monitor_mutex
      }
    end

  end

end

___

A tip of my hat to my former professor and advisor, Doug Lea, who wrote the original java.util.concurrent package.

Submit a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>