Tuesday, December 13, 2005
Friday, December 09, 2005
ruby, singleton, locking, etc
I came across the following problem the other day: implementing the singleton pattern for a multi-threaded ruby app. Turns out that ruby has a singleton class that does just that...
require 'singleton' class SingletonTest include Singleton def initialize puts 'start initialize ' sleep 2 puts 'end initialize' end end a = b = c = d = nil t1 = Thread.new { a = SingletonTest.instance b = SingletonTest.instance } t2 = Thread.new { c = SingletonTest.instance d = SingletonTest.instance } t1.join t2.join puts a.to_s puts b.to_s puts c.to_s puts d.to_sThis works perfect. No race conditions occur and only one instance of the object gets created. However I couldn't seem to figure out how to get the object to use anything besides that default initialize method without any arguments. I needed something that would accept values that would be used when the object is first created. I'm not sure why I couldn't get the initialize method to accept arguments, it seems to me that by defining initialize myself would be enough but I guess not. So I went about implementing this myself...
class SingletonTest @@singleton_instance = nil def SingletonTest.get_instance puts 'getting instance' if(!@@singleton_instance) sleep 2 puts ' instance created' @@singleton_instance = SingletonTest.new else puts ' returning already created instance' end return @@singleton_instance end end st2 = st1 = nil t1 = Thread.new {st1 = SingletonTest.get_instance} t2 = Thread.new {st2 = SingletonTest.get_instance} t1.join t2.join puts st1.to_s puts st2.to_sThis uses a class variable and method. It works fine in single threaded application, but feel flat on its face with multiple threads. So I started poking around with some of the locking mechanisms that ruby has... I started off with monitors:
require 'monitor' class TestSingleton < Monitor @@test_object = nil def getInstance(s) synchronize do if !@@test_object return @@test_object = TestObject.new else return @@test_object end end end end class TestObject def initialize end endThe problem I found with the ruby implementation of monitors is the fact you have to extent the monitor class and then create a new instance of the class before you can synchronize a block of code. So you can't use 'synchronize do' inside of a class method (unless perhaps you were dealing with an instance of the class), which just makes things messy here. So I threw out the monitor idea and tried using ruby's Mutex class:
require 'thread' class SingletonTest @@mutex = Mutex.new @@object_instance = nil def SingletonTest.get_instance s puts 'getting instance '+s.to_s @@mutex.synchronize do if !@@object_instance puts ' creating instance' sleep 2 @@object_instance = SingletonTest.new end return @@object_instance end end end st1 = st2 = st3 = st4 = nil t1 = Thread.new {st1 = SingletonTest.get_instance '1'} t2 = Thread.new {st2 = SingletonTest.get_instance '2'} t1.join t2.join puts st1.to_s puts st2.to_sInstead of extending a class like monitors, you just create an instance of Mutex and use it to create synchronized blocks of code. This seems to work for me. I'm not sure that this is the best implementation, but until I find something better this will do.