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_s
This 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_s
This 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
end
The 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_s
Instead 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.

0 Comments:
Post a Comment
<< Home