Ruby Wait / Notify / NotifyAll
While 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.