On 6/16/07, Trans [email protected] wrote:
}
not?
Yes, this is thread safe… each thread that calls #go gets its own
set of objects (TypeRunners) that are separate from the objects in the
other threads.
Also, I was thinking. If it’s thread safe to just pass type as an
argument, one would think there could be a way to define a “locked”
instance var that is essentially the same thing but without all the
overhead.
It seems like it should be simple, but there’s a lot of complexity
when programming multithreaded code. In your example you’re not
changing the state of anything, so that’s why you can simplify
things… but things get out of hand fast. See my comments past the
end of the code below.
Here’s my attempt at a very simple way to do what you’re talking
about… I had trouble getting the methods put in the right namespace,
so maybe my module code isn’t the most elegant but it does what I want
it to do.
require ‘thread’
module Locker
def self.included mod
(class << mod; self; end).instance_eval do
define_method(:attr_locked) do |*names|
class_eval do
@@locks ||= {}
names.each do |name|
# make a lock for each attribute
@@locks[name] = Mutex.new
# getter
define_method(name) do
@@locks[name].synchronize do
instance_variable_get("@#{name}")
end
end
# setter
define_method("#{name}=") do |value|
@@locks[name].synchronize do
instance_variable_set("@#{name}", value)
sleep 5
end
end
end
end # class_eval
end # attr_locked
end # instance_eval
end
end
class DooWop
include Locker
attr_locked :chord, :name
def initialize name, chord
@name, @chord = name, chord
end
end
x = DooWop.new(‘The Platters’, ‘Fmaj6/9’)
$stdout.sync = true
def tprint string
print “#{Thread.current} #{string}\n”
end
Thread.new{ tprint “1. #{x.name}” }
this will block access to x.name for 5 seconds
Thread.new{ tprint “2. setting name”; x.name = ‘The Orioles’ }
this will wait for the above thread to finish
Thread.new{ sleep 1; tprint “3. #{x.name}” }
this isn’t blocked
Thread.new{ tprint “4. #{x.chord}” }
this will block access to x.chord for 5 seconds
Thread.new do
tprint “5. setting chord”
x.chord = ‘Dm7b5’
tprint “5. #{x.chord}”
end
this could be indeterminite, we didn’t wait for the writer to finish
Thread.new{ tprint “6. unsafe: #{x.instance_eval{@name}}” }
sleep 0.5 until Thread.list.size == 1 # should be joining here instead
This is helpful only in the simplest cases. First it blocks everyone
while in the getter or setter… ideally we shouldn’t make the getters
wait for the other getters to finish. Next, if you try to read the
variable more than once in a method, you can still end up with
different values.
You’d need to wrap the section of code that involves the var in a
#synchronize block… but wrap the least amount of code necessary
because you want to hurry up and let the other threads do their work.
There’s no way to “automate” that. The code above is only useful in
the most simple applications.
Further, we’ve only been talking about one variable at a time. Often
you’ll need to lock several variables, like maybe we are going to look
up on Google which songs by @name have the chord @chord. We’ll need
to get those values at the same time because if we read chord,
(another thread interrupts here and changes @name), then read name…
that’s a problem, and our “locked” variables can’t do anything to
help.
There was a thread on this list a few days ago with a title like
“synchronized attr”, I only skimmed it but I think they were talking
about why you can’t really boil thread synchronization down to a
one-size-fits-all solution. There are a few data structures (Monitor,
ConditionVariable, Queue, etc) that really help out, but there’s no
way to just call some method to magically make your code thread
safe… yet?
Regards,
Erwin